Skip to content

Instantly share code, notes, and snippets.

@RossPfeiffer
Created September 18, 2020 16:34
Show Gist options
  • Save RossPfeiffer/da944582ece895dd1d7b132e1a21bbbe to your computer and use it in GitHub Desktop.
Save RossPfeiffer/da944582ece895dd1d7b132e1a21bbbe to your computer and use it in GitHub Desktop.
pragma solidity ^ 0.6.6;
/*
// ""--.._
|| (_) _ "-._
|| _ (_) '-.
|| (_) __..-'
\\__..--""
*/
contract ColorToken{
mapping(address => uint256) public balances;
mapping(address => uint256) public red;
mapping(address => uint256) public green;
mapping(address => uint256) public blue;
uint public _totalSupply;
mapping(address => mapping(address => uint)) approvals;
event Transfer(
address indexed from,
address indexed to,
uint256 amount,
bytes data
);
function totalSupply() public view returns (uint256) {
return _totalSupply;
}
function balanceOf(address _owner) public view returns (uint256 balance) {
return balances[_owner];
}
function addColor(address addr, uint color, uint _red, uint _green, uint _blue) internal {
red[addr] += _red * color;
green[addr] += _green * color;
blue[addr] += _blue * color;
}
function RGB_Ratio() public view returns(uint,uint,uint){
return RGB_Ratio(msg.sender);
}
function RGB_Ratio(address addr) public view returns(uint,uint,uint){
uint coloredWeight = balances[addr];
if (coloredWeight==0){
return (0,0,0);
}
return ( red[addr]/coloredWeight, green[addr]/coloredWeight, blue[addr]/coloredWeight);
}
function RGB_scale(address addr, uint numerator, uint denominator) internal view returns(uint,uint,uint){
return (red[addr] * numerator / denominator, green[addr] * numerator / denominator, blue[addr] * numerator / denominator);
}
// Function that is called when a user or another contract wants to transfer funds.
function transfer(address _to, uint _value, bytes memory _data) public virtual returns (bool) {
if( Common.isContract(_to) ){
return transferToContract(_to, _value, _data);
}else{
return transferToAddress(_to, _value, _data);
}
}
// Standard function transfer similar to ERC20 transfer with no _data.
// Added due to backwards compatibility reasons .
function transfer(address _to, uint _value) public virtual returns (bool) {
//standard function transfer similar to ERC20 transfer with no _data
//added due to backwards compatibility reasons
bytes memory empty;
if(Common.isContract(_to)){
return transferToContract(_to, _value, empty);
}else{
return transferToAddress(_to, _value, empty);
}
}
//function that is called when transaction target is an address
function transferToAddress(address _to, uint _value, bytes memory _data) private returns (bool) {
moveTokens(msg.sender, _to, _value);
emit Transfer(msg.sender, _to, _value, _data);
return true;
}
//function that is called when transaction target is a contract
function transferToContract(address _to, uint _value, bytes memory _data) private returns (bool) {
moveTokens(msg.sender, _to, _value);
ERC223ReceivingContract receiver = ERC223ReceivingContract(_to);
receiver.tokenFallback(msg.sender, _value, _data);
emit Transfer(msg.sender, _to, _value, _data);
return true;
}
function moveTokens(address _from, address _to, uint _amount) internal virtual{
(uint red_ratio, uint green_ratio, uint blue_ratio) = RGB_scale( _from, _amount, balances[_from] );
red[_from] -= red_ratio;
green[_from] -= green_ratio;
blue[_from] -= blue_ratio;
red[_to] += red_ratio;
green[_to] += green_ratio;
blue[_to] += blue_ratio;
balances[_from] -= _amount;
balances[_to] += _amount;
}
function allowance(address src, address guy) public view returns (uint) {
return approvals[src][guy];
}
function transferFrom(address src, address dst, uint amount) public returns (bool){
address sender = msg.sender;
require(approvals[src][sender] >= amount);
if (src != sender) {
approvals[src][sender] -= amount;
}
moveTokens(src,dst,amount);
return true;
}
event Approval(address indexed src, address indexed guy, uint amount);
function approve(address guy, uint amount) public returns (bool) {
address sender = msg.sender;
approvals[sender][guy] = amount;
emit Approval( sender, guy, amount );
return true;
}
}
contract PiZZa is ColorToken{
// scaleFactor is used to convert Ether into bonds and vice-versa: they're of different
// orders of magnitude, hence the need to bridge between the two.
uint256 constant scaleFactor = 0x10000000000000000;
address payable address0 = address(0);
uint256 constant internal tokenPriceInitial_ = 0.0000001 ether;
uint256 constant internal tokenPriceIncremental_ = 0.00000001 ether;
// Typical values that we have to declare.
string constant public name = "Pizza Token";
string constant public symbol = "PiZZa";
uint8 constant public decimals = 18;
mapping(address => uint256) public average_ethSpent;
// For calculating hodl multiplier that factors into resolves minted
mapping(address => uint256) public average_buyInTimeSum;
// Array between each address and their number of resolves being staked.
mapping(address => uint256) public resolveWeight;
// Array between each address and how much Ether has been paid out to it.
// Note that this is scaled by the scaleFactor variable.
mapping(address => int256) public payouts;
// The total number of resolves being staked in this contract
uint256 public dissolvingResolves;
// For calculating the hodl multiplier. Weighted average release time
uint public sumOfInputETH;
uint public sumOfInputTime;
uint public sumOfOutputETH;
uint public sumOfOutputTime;
// Something about invarience.
int256 public earningsOffset;
// Variable tracking how much Ether each token is currently worth.
// Note that this is scaled by the scaleFactor variable.
uint256 public earningsPerResolve;
//The resolve token contract
Crust public resolveToken;
constructor() public{
resolveToken = new Crust( address(this) );
}
function fluxFee(uint paidAmount) public view returns (uint fee) {
uint totalResolveSupply = resolveToken.totalSupply() - resolveToken.balanceOf( address(0) );
if ( dissolvingResolves == 0 )
return 0;
return paidAmount * ( totalResolveSupply - dissolvingResolves ) / totalResolveSupply * sumOfOutputETH / sumOfInputETH;
}
// Converts the Ether accrued as resolveEarnings back into bonds without having to
// withdraw it first. Saves on gas and potential price spike loss.
event Reinvest( address indexed addr, uint256 reinvested, uint256 dissolved, uint256 bonds, uint256 resolveTax);
function reinvestEarnings(uint amountFromEarnings) public returns(uint,uint){
address sender = msg.sender;
// Retrieve the resolveEarnings associated with the address the request came from.
uint upScaleDivs = (uint)((int256)( earningsPerResolve * resolveWeight[sender] ) - payouts[sender]);
uint totalEarnings = upScaleDivs / scaleFactor;//resolveEarnings(sender);
require(amountFromEarnings <= totalEarnings, "the amount exceeds total earnings");
uint oldWeight = resolveWeight[sender];
resolveWeight[sender] = oldWeight * (totalEarnings - amountFromEarnings) / totalEarnings;
uint weightDiff = oldWeight - resolveWeight[sender];
resolveToken.transfer( address0, weightDiff );
dissolvingResolves -= weightDiff;
// something about invariance
int withdrawnEarnings = (int)(upScaleDivs * amountFromEarnings / totalEarnings) - (int)(weightDiff*earningsPerResolve);
payouts[sender] += withdrawnEarnings;
// Increase the total amount that's been paid out to maintain invariance.
earningsOffset += withdrawnEarnings;
// Assign balance to a new variable.
uint value_ = (uint) (amountFromEarnings);
// If your resolveEarnings are worth less than 1 szabo, abort.
if (value_ < 0.000001 ether)
revert();
// Calculate the fee
uint fee = fluxFee(value_);
// The amount of Ether used to purchase new bonds for the caller
uint numEther = value_ - fee;
//resolve reward tracking stuff
average_ethSpent[sender] += numEther;
average_buyInTimeSum[sender] += now * scaleFactor * numEther;
sumOfInputETH += numEther;
sumOfInputTime += now * scaleFactor * numEther;
// The number of bonds which can be purchased for numEther.
uint createdBonds = ethereumToTokens_(numEther);
uint[] memory RGB = new uint[](3);
(RGB[0], RGB[1], RGB[2]) = RGB_Ratio(sender);
addColor(sender, createdBonds, RGB[0], RGB[1], RGB[2]);
// the variable stoLOGC the amount to be paid to stakers
uint resolveFee;
// Check if we have bonds in existence
if ( dissolvingResolves > 0 ) {
resolveFee = fee * scaleFactor;
// Fee is distributed to all existing resolve stakers before the new bonds are purchased.
// rewardPerResolve is the amount(ETH) gained per resolve token from this purchase.
uint rewardPerResolve = resolveFee / dissolvingResolves;
// The Ether value per token is increased proportionally.
earningsPerResolve += rewardPerResolve;
}
// Add the createdBonds to the total supply.
_totalSupply += createdBonds;
// Assign the bonds to the balance of the buyer.
balances[sender] += createdBonds;
emit Reinvest(sender, value_, weightDiff, createdBonds, resolveFee);
return (createdBonds, weightDiff);
}
// Sells your bonds for Ether
function sellAllBonds() public returns(uint returned_eth, uint returned_resolves, uint initialInput_ETH){
return sell( balanceOf(msg.sender) );
}
function sellBonds(uint amount) public returns(uint returned_eth, uint returned_resolves, uint initialInput_ETH){
uint balance = balanceOf(msg.sender);
require(balance >= amount, "Amount is more than balance");
( returned_eth, returned_resolves, initialInput_ETH ) = sell(amount);
return (returned_eth, returned_resolves, initialInput_ETH);
}
// Big red exit button to pull all of a holder's Ethereum value from the contract
function getMeOutOfHere() public {
sellAllBonds();
withdraw( resolveEarnings(msg.sender) );
}
// Gatekeeper function to check if the amount of Ether being sent isn't too small
function fund() payable public returns(uint createdBonds){
uint[] memory RGB = new uint[](3);
(RGB[0], RGB[1], RGB[2]) = RGB_Ratio(msg.sender);
return buy(msg.sender, RGB[0], RGB[1], RGB[2]);
}
// Calculate the current resolveEarnings associated with the caller address. This is the net result
// of multiplying the number of resolves held by their current value in Ether and subtracting the
// Ether that has already been paid out.
function resolveEarnings(address _owner) public view returns (uint256 amount) {
return (uint256) ((int256)(earningsPerResolve * resolveWeight[_owner]) - payouts[_owner]) / scaleFactor;
}
event Buy( address indexed addr, uint256 spent, uint256 bonds, uint256 resolveTax);
function buy(address addr, uint _red, uint _green, uint _blue) public payable returns(uint createdBonds){
if(_red>1e18) _red = 1e18;
if(_green>1e18) _green = 1e18;
if(_blue>1e18) _blue = 1e18;
// Any transaction of less than 1 szabo is likely to be worth less than the gas used to send it.
if ( msg.value < 0.000001 ether )
revert();
// Calculate the fee
uint fee = fluxFee(msg.value);
// The amount of Ether used to purchase new bonds for the caller.
uint numEther = msg.value - fee;
//resolve reward tracking stuff
uint currentTime = now;
average_ethSpent[addr] += numEther;
average_buyInTimeSum[addr] += currentTime * scaleFactor * numEther;
sumOfInputETH += numEther;
sumOfInputTime += currentTime * scaleFactor * numEther;
// The number of bonds which can be purchased for numEther.
createdBonds = ethereumToTokens_(numEther);
addColor(addr, createdBonds, _red, _green, _blue);
// Add the createdBonds to the total supply.
_totalSupply += createdBonds;
// Assign the bonds to the balance of the buyer.
balances[addr] += createdBonds;
// Check if we have bonds in existence
uint resolveFee;
if (dissolvingResolves > 0) {
resolveFee = fee * scaleFactor;
// Fee is distributed to all existing resolve holders before the new bonds are purchased.
// rewardPerResolve is the amount gained per resolve token from this purchase.
uint rewardPerResolve = resolveFee/dissolvingResolves;
// The Ether value per resolve is increased proportionally.
earningsPerResolve += rewardPerResolve;
}
emit Buy( addr, msg.value, createdBonds, resolveFee);
return createdBonds;
}
function avgHodl() public view returns(uint hodlTime){
return now - (sumOfInputTime - sumOfOutputTime) / (sumOfInputETH - sumOfOutputETH) / scaleFactor;
}
function getReturnsForBonds(address addr, uint bondsReleased) public view returns(uint etherValue, uint mintedResolves, uint new_releaseTimeSum, uint new_releaseAmount, uint initialInput_ETH){
uint output_ETH = tokensToEthereum_(bondsReleased);
uint input_ETH = average_ethSpent[addr] * bondsReleased / balances[addr];
// hodl multiplier. because if you don't hodl at all, you shouldn't be rewarded resolves.
// and the multiplier you get for hodling needs to be relative to the average hodl
uint buyInTime = average_buyInTimeSum[addr] / average_ethSpent[addr];
uint cashoutTime = now * scaleFactor - buyInTime;
uint new_sumOfOutputTime = sumOfOutputTime + average_buyInTimeSum[addr] * bondsReleased / balances[addr];
uint new_sumOfOutputETH = sumOfOutputETH + input_ETH; //It's based on the original ETH, so that's why input_ETH is used. Not output_ETH.
uint averageHoldingTime = now * scaleFactor - ( sumOfInputTime - sumOfOutputTime ) / ( sumOfInputETH - sumOfOutputETH );
return (output_ETH, input_ETH * cashoutTime / averageHoldingTime * input_ETH / output_ETH, new_sumOfOutputTime, new_sumOfOutputETH, input_ETH);
}
event Sell( address indexed addr, uint256 bondsSold, uint256 cashout, uint256 resolves, uint256 resolveTax, uint256 initialCash);
function sell(uint256 amount) internal returns(uint eth, uint resolves, uint initialInput){
address payable sender = msg.sender;
// Calculate the amount of Ether & Resolves that the holder's bonds sell for at the current sell price.
uint[] memory UINTs = new uint[](5);
(
UINTs[0]/*ether before fee*/,
UINTs[1]/*minted resolves*/,
UINTs[2]/*new_sumOfOutputTime*/,
UINTs[3]/*new_sumOfOutputETH*/,
UINTs[4]/*initialInput_ETH*/) = getReturnsForBonds(sender, amount);
// calculate the fee
uint fee = fluxFee(UINTs[0]/*ether before fee*/);
// magic distribution
uint[] memory RGB = new uint[](3);
(RGB[0], RGB[1], RGB[2]) = RGB_Ratio(sender);
resolveToken.mint(sender, UINTs[1]/*minted resolves*/, RGB[0], RGB[1], RGB[2]);
// update weighted average cashout time
sumOfOutputTime = UINTs[2]/*new_sumOfOutputTime*/;
sumOfOutputETH = UINTs[3] /*new_sumOfOutputETH*/;
// reduce the amount of "eth spent" based on the percentage of bonds being sold back into the contract
average_ethSpent[sender] = average_ethSpent[sender] * ( balances[sender] - amount) / balances[sender];
// reduce the "buyInTime" sum that's used for average buy in time
average_buyInTimeSum[sender] = average_buyInTimeSum[sender] * (balances[sender] - amount) / balances[sender];
// Net Ether for the seller after the fee has been subtracted.
uint numEthers = UINTs[0]/*ether before fee*/ - fee;
// Burn the bonds which were just sold from the total supply.
_totalSupply -= amount;
// maintain color density
thinColor( sender, balances[sender] - amount, balances[sender]);
// Remove the bonds from the balance of the buyer.
balances[sender] -= amount;
// Check if we have bonds in existence
uint resolveFee;
if ( dissolvingResolves > 0 ){
// Scale the Ether taken as the selling fee by the scaleFactor variable.
resolveFee = fee * scaleFactor;
// Fee is distributed to all remaining resolve holders.
// rewardPerResolve is the amount gained per resolve thanks to this sell.
uint rewardPerResolve = resolveFee/dissolvingResolves;
// The Ether value per resolve is increased proportionally.
earningsPerResolve += rewardPerResolve;
}
(bool success, ) = sender.call{value:numEthers}("");
require(success, "Transfer failed.");
emit Sell( sender, amount, numEthers, UINTs[1]/*minted resolves*/, resolveFee, UINTs[4] /*initialInput_ETH*/);
return (numEthers, UINTs[1]/*minted resolves*/, UINTs[4] /*initialInput_ETH*/);
}
function thinColor(address addr, uint newWeight, uint oldWeight) internal{
(red[addr], green[addr], blue[addr]) = RGB_scale( addr, newWeight, oldWeight);
}
// Allow contract to accept resolve tokens
event StakeResolves( address indexed addr, uint256 amountStaked, bytes _data );
function tokenFallback(address from, uint value, bytes calldata _data) external{
if(msg.sender == address(resolveToken) ){
resolveWeight[from] += value;
dissolvingResolves += value;
// Then we update the payouts array for the "resolve shareholder" with this amount
int payoutDiff = (int256) (earningsPerResolve * value);
payouts[from] += payoutDiff;
earningsOffset += payoutDiff;
emit StakeResolves(from, value, _data);
}else{
revert("no want");
}
}
// Withdraws resolveEarnings held by the caller sending the transaction, updates
// the requisite global variables, and transfers Ether back to the caller.
event Withdraw( address indexed addr, uint256 earnings, uint256 dissolve );
function withdraw(uint amount) public returns(uint){
address payable sender = msg.sender;
// Retrieve the resolveEarnings associated with the address the request came from.
uint upScaleDivs = (uint)((int256)( earningsPerResolve * resolveWeight[sender] ) - payouts[sender]);
uint totalEarnings = upScaleDivs / scaleFactor;
require( amount <= totalEarnings && amount > 0 );
uint oldWeight = resolveWeight[sender];
resolveWeight[sender] = oldWeight * ( totalEarnings - amount ) / totalEarnings;
uint weightDiff = oldWeight - resolveWeight[sender];
resolveToken.transfer( address0, weightDiff);
dissolvingResolves -= weightDiff;
// something about invariance
int withdrawnEarnings = (int)(upScaleDivs * amount / totalEarnings) - (int)(weightDiff*earningsPerResolve);
payouts[sender] += withdrawnEarnings;
// Increase the total amount that's been paid out to maintain invariance.
earningsOffset += withdrawnEarnings;
// Send the resolveEarnings to the address that requested the withdraw.
(bool success, ) = sender.call{value: amount}("");
require(success, "Transfer failed.");
emit Withdraw( sender, amount, weightDiff);
return weightDiff;
}
event PullResolves( address indexed addr, uint256 pulledResolves, uint256 forfeiture);
function pullResolves(uint amount) public returns (uint forfeiture){
address sender = msg.sender;
uint resolves = resolveWeight[ sender ];
require(amount <= resolves && amount > 0);
require(amount < dissolvingResolves);//"you can't forfeit the last resolve"
uint yourTotalEarnings = (uint)((int256)(resolves * earningsPerResolve) - payouts[sender]);
uint forfeitedEarnings = yourTotalEarnings * amount / resolves;
// Update the payout array so that the "resolve shareholder" cannot claim resolveEarnings on previous staked resolves.
payouts[sender] += (int256)(forfeitedEarnings) - (int256)(earningsPerResolve * amount);
resolveWeight[sender] -= amount;
dissolvingResolves -= amount;
// The Ether value per token is increased proportionally.
earningsPerResolve += forfeitedEarnings / dissolvingResolves;
resolveToken.transfer( sender, amount );
emit PullResolves( sender, amount, forfeitedEarnings / scaleFactor);
return forfeitedEarnings / scaleFactor;
}
function moveTokens(address _from, address _to, uint _amount) internal override{
uint totalBonds = balances[_from];
require(_amount <= totalBonds && _amount > 0);
uint ethSpent = average_ethSpent[_from] * _amount / totalBonds;
uint buyInTimeSum = average_buyInTimeSum[_from] * _amount / totalBonds;
average_ethSpent[_from] -= ethSpent;
average_buyInTimeSum[_from] -= buyInTimeSum;
balances[_from] -= _amount;
average_ethSpent[_to] += ethSpent;
average_buyInTimeSum[_to] += buyInTimeSum;
balances[_to] += _amount;
super.moveTokens(_from, _to, _amount);
}
function buyPrice()
public
view
returns(uint256)
{
// our calculation relies on the token supply, so we need supply. Doh.
if(_totalSupply == 0){
return tokenPriceInitial_ + tokenPriceIncremental_;
} else {
uint256 _ethereum = tokensToEthereum_(1e18);
uint256 _dividends = fluxFee(_ethereum );
uint256 _taxedEthereum = _ethereum + _dividends;
return _taxedEthereum;
}
}
function sellPrice()
public
view
returns(uint256)
{
// our calculation relies on the token supply, so we need supply. Doh.
if(_totalSupply == 0){
return tokenPriceInitial_ - tokenPriceIncremental_;
} else {
uint256 _ethereum = tokensToEthereum_(1e18);
uint256 _dividends = fluxFee(_ethereum );
uint256 _taxedEthereum = Common.subtract(_ethereum, _dividends);
return _taxedEthereum;
}
}
function calculateTokensReceived(uint256 _ethereumToSpend)
public
view
returns(uint256)
{
uint256 _dividends = fluxFee(_ethereumToSpend);
uint256 _taxedEthereum = Common.subtract(_ethereumToSpend, _dividends);
uint256 _amountOfTokens = ethereumToTokens_(_taxedEthereum);
return _amountOfTokens;
}
function calculateEthereumReceived(uint256 _tokensToSell)
public
view
returns(uint256)
{
require(_tokensToSell <= _totalSupply);
uint256 _ethereum = tokensToEthereum_(_tokensToSell);
uint256 _dividends = fluxFee(_ethereum );
uint256 _taxedEthereum = Common.subtract(_ethereum, _dividends);
return _taxedEthereum;
}
function ethereumToTokens_(uint256 _ethereum)
internal
view
returns(uint256)
{
uint256 _tokenPriceInitial = tokenPriceInitial_ * 1e18;
uint256 _tokensReceived =
(
(
// underflow attempts BTFO
Common.subtract(
(sqrt
(
(_tokenPriceInitial**2)
+
(2*(tokenPriceIncremental_ * 1e18)*(_ethereum * 1e18))
+
(((tokenPriceIncremental_)**2)*(_totalSupply**2))
+
(2*(tokenPriceIncremental_)*_tokenPriceInitial*_totalSupply)
)
), _tokenPriceInitial
)
)/(tokenPriceIncremental_)
)-(_totalSupply)
;
return _tokensReceived;
}
function tokensToEthereum_(uint256 _tokens)
internal
view
returns(uint256)
{
uint256 tokens_ = (_tokens + 1e18);
uint256 _tokenSupply = (_totalSupply + 1e18);
uint256 _etherReceived =
(
// underflow attempts BTFO
Common.subtract(
(
(
(
tokenPriceInitial_ +(tokenPriceIncremental_ * (_tokenSupply/1e18))
)-tokenPriceIncremental_
)*(tokens_ - 1e18)
),(tokenPriceIncremental_*((tokens_**2-tokens_)/1e18))/2
)
/1e18);
return _etherReceived;
}
function sqrt(uint x) internal pure returns (uint y) {
uint z = (x + 1) / 2;
y = x;
while (z < y) {
y = z;
z = (x / z + z) / 2;
}
}
}
abstract contract ERC223ReceivingContract{
function tokenFallback(address _from, uint _value, bytes calldata _data) external virtual;
}
contract Crust is ColorToken{
string public name = "Color";
string public symbol = "`c";
uint8 constant public decimals = 18;
address public hourglass;
constructor(address _hourglass) public{
hourglass = _hourglass;
}
modifier hourglassOnly{
require(msg.sender == hourglass);
_;
}
event Transfer(
address indexed from,
address indexed to,
uint256 amount,
bytes data
);
event Mint(
address indexed addr,
uint256 amount
);
function mint(address _address, uint _value, uint _red, uint _green, uint _blue) external hourglassOnly(){
balances[_address] += _value;
_totalSupply += _value;
addColor(_address, _value, _red, _green, _blue);
emit Mint(_address, _value);
}
}
/**
* @title Common
* @dev Math operations with safety checks that throw on error
*/
library Common {
/**
* @dev Substracts two numbers, throws on overflow (i.e. if subtrahend is greater than minuend).
*/
function subtract(uint256 a, uint256 b) internal pure returns (uint256) {
assert(b <= a);
return a - b;
}
//assemble the given address bytecode. If bytecode exists then the _addr is a contract.
function isContract(address _addr) public view returns (bool is_contract) {
uint length;
assembly {
//retrieve the size of the code on target address, this needs assembly
length := extcodesize(_addr)
}
if(length>0) {
return true;
}else {
return false;
}
}
}
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment