A Decaying Token - Building Catallax on Ethereum - Week 3

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:

  1. 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

  2. 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.

  3. 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.

  4. In increase our catchUpBlock for our account we need to catch it up.  Call CatchUp with your account number.  Ie 0xca35b7d915458ef540ade6068dfe2f44e8fa733c

  5. Check your balanceOf your account and you should see that it is 70,000,000 tokens lighter.  These tokens are in the demurrageHold variable.

  6. Try your transfer from #3 above.  It should go through this time.

  7.  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);

}