The last couple of weeks of chasing rabbit trails to try to figure out how to deliver pref payments out to accounts once a Catallax account has caught up has settled down this week due to making the following assumption:
An outside authority is going to have to keep track of and distribute pref payments.
I’ll get back to this in future development. I may even find an on-chain solution, but until then I’m going to just table it and build out some of the other features.
Today I’m publishing some actual working solidity code that runs a decaying token.
You can see the contract at the bottom of this article or here:
https://gist.github.com/anonymous/7fc3f3a48ad3e8b26aa1a7c55030c64f
You can load it up in browser solidity here:
https://ethereum.github.io/browser-solidity/#gist=7fc3f3a48ad3e8b26aa1a7c55030c64f
I’ve stripped out the extraneous erc20 stuff and just focused on balances at the moment. We are doing something pretty basic here. We have a structure called a rateMap (mapping(uint -> Rate)) that is going to keep track of ranges of blocks and an associated decay rate. If your account isn’t caught up to the latest maxCatchUpBlock, you can’t spend your tokens and must first ‘catch up’ before you can spend again.
In this example, we are trusting and outside authority to publish reliable rates and currently, the decayed tokens just collect in an internal variable in the contract(demurrageHold).
The Rate Struct looks like this:
struct Rate {
uint256 nextblock;
uint256 rate;
bool initilized;
}
The nextblock variable points to the place in the rateMap mapping where you can find the next rate. The initialized bool is used to validate that a rate that you have found in your rateMap is a valid rate. Empty mappings will return an uninitialized struct so this lets us look up a rate and know that it is invalid.
When we start our contract we seed the rateMap like this:
//record the startblock that the currency came online -- used for cycling through rates
startBlock = block.number;
//create the first rate that goes from block 0 to the start block
rateMap[0].initialized = true;
rateMap[0].rate = 0;
rateMap[0].nextblock = startBlock;
//init the next rate
rateMap[startBlock].initialized = true;
//set the max catchup to the current block
maxCatchUpBlock = block.number;
rateBase = 10000000; //base used for rates.
So starting off we will have a rate from 0 to our startBlock of 0 and we will be waiting for the rate to be added with an initialized rate at the startBlock.
You can observe the decaying currency by executing the following in browser solidity:
Publish the contract with a call of 1000000000,””,2,”” - this creates a billion tokens and puts them in originator’s account. Let’s assume this is 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
Note the startBlock variable and capture that. We’re going to add our first decay rate by calling addRate with StartBlock, StartBlock + 100, 700000. Ie: 1150000,1150100, 700000 -Note that the rate we are putting in (700000) will be made a percentage by dividing by the baseRate Variable in our contract. So in this instance we are decaying 7% over a 100 block period. Pretty steep, but good for our example.
Try transferring some tokens to another address by calling transfer(Ie: "0x14723a09acff6d2a60dcdf7aa4aff308fddc160c", 100). The function should throw since the maxCatchUpBlock has increased from our current maxCatcUpBlock for our original address of 0.
In increase our catchUpBlock for our account we need to catch it up. Call CatchUp with your account number. Ie 0xca35b7d915458ef540ade6068dfe2f44e8fa733c
Check your balanceOf your account and you should see that it is 70,000,000 tokens lighter. These tokens are in the demurrageHold variable.
Try your transfer from #3 above. It should go through this time.
Notice that in the transfer we have to initialize the new account that is created with a catchUpBlock equal to the maxCatchUpBlock(1150100). If we didn’t do this then the first time this account caught up it would start at block 0 even though it didn’t have any tokens from 1150000 to 1150100.
This token doesn’t do much except decay at a rate specified by the publisher. There is a lot of work left to do, but it is nice to have some actual code that does something interesting.
Next week I’ll get back to putting some of the infrastructure necessary to issue out pref payments and maybe start looking at how to determine what those payments are processing the blockchain.
If you have any comments or questions, please leave them below. If you’d like to see more of this kind of stuff and help us build this new economy please consider supporting the "Catallax Code " patreon.
If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality.
Donations always accepted at:
BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas
ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0
//© copyright 2017 - Catallax Bank and Rivvir Consulting
// Developed by Austin Fatheree
// http://catallax.info @hypercatallax
// All rights reserved.
pragma solidity ^0.4.8;
contract DecayToken {
struct Rate {
uint256 nextblock;
uint256 rate;
bool initialized;
}
uint256 public totalSupply;
uint256 public startBlock; //holds the generation block for the contract so that we know where to start our linked list for decay rates
uint256 public demurrageHold;
uint256 public rateBase;
uint256 maxCatchUpBlock;
mapping(address => uint256) balance;
mapping(address => uint256) catchUpBlock;
mapping(uint256 => Rate) rateMap;
string public name; //fancy name: eg Simon Bucks
uint8 public decimals; //How many decimals to show. ie. There could 1000 base units with 3 decimals. Meaning 0.980 SBX = 980 base units. It's like comparing 1 wei to 1 ether.
string public symbol; //An identifier: eg SBX
string public version = 'H0.1'; //human 0.1 standard. Just an arbitrary versioning scheme.
function () {
//if ether is sent to this address, send it back.
throw;
}
function DecayToken(
uint256 _initialAmount,
string _tokenName,
uint8 _decimalUnits,
string _tokenSymbol
){
balance[msg.sender] = _initialAmount; // Give the creator all initial tokens
totalSupply = _initialAmount; // Update total supply
name = _tokenName; // Set the name for display purposes
decimals = _decimalUnits; // Amount of decimals for display purposes
symbol = _tokenSymbol; // Set the symbol for display purposes
//record the startblock that the currency came online -- used for cycling through rates
startBlock = block.number;
//create the first rate that goes from block 0 to the start block
rateMap[0].initialized = true;
rateMap[0].rate = 0;
rateMap[0].nextblock = startBlock;
//init the next rate
rateMap[startBlock].initialized = true;
//set the max catchup to the current block
maxCatchUpBlock = block.number;
rateBase = 10000000; //base used for rates.
}
//erc20 interface
function balanceOf( address who ) constant returns (uint value){
//todo: check validity
return balance[who];
}
function transfer( address to, uint value) returns (bool ok){
if(value == 0) throw;
//test catch up
if(catchUpBlock[msg.sender] < maxCatchUpBlock) throw;
//make sure balance is positive
if(balance[msg.sender] < value) throw;
//update balances
balance[msg.sender] = balance[msg.sender] - value;
//if this is a new account or the previous balance was 0, catch it up by default
if(balance[to] == 0){
catchUpBlock[to] = maxCatchUpBlock;
}
balance[to] = balance[to] + value;
Transfer(msg.sender, to, value);
return true;
}
function addRate(uint _startBlock, uint _endBlock, uint amount) returns (bool ok){
if(rateMap[_startBlock].initialized == false) throw; //a rate doesnt exist for this startblock
if(rateMap[_endBlock].initialized != false) throw; //we cant correct rates here only append
//set the new rate
rateMap[_startBlock].nextblock = _endBlock;
rateMap[_startBlock].rate = amount;
rateMap[_endBlock].initialized = true;
//update the maxCatchUpBlock for everyoune
maxCatchUpBlock = _endBlock;
return true;
}
function getRate(uint _startBlock) constant returns(uint _TheRate, uint _EndBlock){
_TheRate = rateMap[_startBlock].rate;
_EndBlock = rateMap[_startBlock].nextblock;
}
function catchup(address account) returns (bool ok){
//todo: issuer can force catchup
if(msg.sender != account) throw;
uint transferAmount = 0; //the amount that will decay
uint nextblock = catchUpBlock[account]; //lookup the current catchup block for this account
uint startBalance = balance[account]; //lookup the current balance of an account
//loop through each rate from the last catch up to maxCatchUpBlock and decay the cash
while(nextblock < maxCatchUpBlock){
Rate thisrate = rateMap[nextblock]; //what was the rate during this period?
uint removeAmount = (startBalance * thisrate.rate)/rateBase; //calculate the amount
transferAmount = transferAmount + removeAmount; //store the amount
startBalance = startBalance - removeAmount; //adjust balance used in calc so we don't go below 0
nextblock = thisrate.nextblock; // update nextblock to advance loop
//todo: scheme to check remaining gas and bail if gas gets too low
}
balance[account] = balance[account] - transferAmount; //update the balance
demurrageHold = demurrageHold + transferAmount; //put the decayed amount into a place for everyone to see
catchUpBlock[account] = maxCatchUpBlock; //update the catchup block allowing the account to spend
CatchUp(account, maxCatchUpBlock, transferAmount); //broadcast catchup so issuer can act
return true;
}
event CatchUp(address indexed from, uint newMaxBlock, uint value);
event Transfer( address indexed from, address indexed to, uint value);
}