Dev Log

DevLog 15 - State minimized implementation on current evm

Cross posted from https://ethresear.ch/t/state-minimized-implementation-on-current-evm/1255

In an effort to implement the functionality described in the post https://ethresear.ch/t/state-minimised-executions/748/26 , I've worked up the following contract for a 'virtual token' that allows for the holding of state in a  Double-batched Merkle log accumulator  as proposed by @JustinDrake.

This is a toy implementation to show what I mean by 'virtual functions'. The idea being that users would call the Transfer() function to emit an event that indicates that they want to transfer.  The Collators respond to that call bo collecting witnesses from the provided dataHash(perhaps via IPFS) and using the vTransfer() function to calculate the logs.

My original thought was to have the virtual function emit logs as it was run in an off chain evm, but with current tooling those logs are discarded or ignored so instead accumulate the new logs in the results bytes.  This makes the code messier, but it generally works.

pragma solidity ^0.4.18;

import "./BytesLib.sol";

contract VirtualToken {

address public owner;
uint256 public totalSupply;

event NewBranch(bytes32 indexed addressFrom, bytes32 amountFrom, bytes32 indexed addressTo, bytes32 amountTo, bytes32 indexed parent);
event SwapLeaf(bytes32 indexed oldLeaf, bytes32 indexed newLeaf);

function VirtualToken() public{
    owner = msg.sender;
    totalSupply = 1000000 * 10**18;
    DataLog(keccak256(address(this), "balance", msg.sender),1,0, bytes32(totalSupply));
}

function getKeyProof(bytes32 key, bytes data) pure returns(bytes32[] result){
    result = new bytes32[](8);
    uint bytePosition = 0;
    while(bytePosition <= data.length){
        uint length = uint256(BytesLib.toBytes32(BytesLib.slice(data, bytePosition, 32)));
        bytes32 thisKey = BytesLib.toBytes32(BytesLib.slice(data, bytePosition + 32, 32));
        if(thisKey == key){
            for(uint thisGroup = 1; thisGroup < length; thisGroup++){
                bytes32 aGroup = BytesLib.toBytes32(BytesLib.slice(data,bytePosition + (32 * thisGroup), 32));

                result[thisGroup - 1] = aGroup;
            }
            return ;
        }
        bytePosition = bytePosition + (32 * length);
    }
}

event DataLog(bytes32 path, uint logType, uint nonce, bytes32 value);

bytes32[8192] public bottomLayer;
uint bottomLayerPosition;
bytes32[] public topLayer;

function pushLog(uint logType, uint nonce, bytes32 value, bytes32[] proof, bytes startBytes) internal returns(bytes result) {
  result = startBytes;
  result = BytesLib.concat(result, BytesLib.fromBytes32(proof[0]));
  result = BytesLib.concat(result, BytesLib.fromBytes32(bytes32(logType)));
  result = BytesLib.concat(result, BytesLib.fromBytes32(bytes32(nonce)));
  result = BytesLib.concat(result, BytesLib.fromBytes32(value));
  //DataLog(proof[0], logType, uint(proof[2]) + 1, value);
}

function pushDel(bytes32[] proof, bytes startBytes) internal returns(bytes result){
    result = startBytes;
    result = pushLog(2, uint(proof[2]), proof[3], proof, result);
}

function pushAdd(bytes32 newValue, bytes32[] proof, bytes startBytes) internal returns(bytes result){
    result = startBytes;
    result = pushLog(1, uint(proof[2]) + 1, newValue, proof, result);
}

function publishCollation(bytes32 newBottomCollation, bytes32 newTopCollation) public {
    if(newTopCollation == 0x0){
        bottomLayer[bottomLayerPosition] = newBottomCollation;
        bottomLayerPosition++;
    } else {
        topLayer.push(newTopCollation);
        bottomLayerPosition = 0;
    }

}


function vTransfer(bytes data) view returns(bytes results){

    address sender = address(getKeyProof(keccak256("msg.sender"), data)[1]);
    bytes32[] memory senderProof = getKeyProof(keccak256(address(this), "balance", sender), data);
    require(verifyProof(keccak256(address(this), "balance", sender), senderProof));
    uint senderBalance =  uint(senderProof[3]);

    address destination = address(getKeyProof(keccak256("destination"), data)[1]);
    bytes32[] memory destinationProof = getKeyProof(keccak256(address(this),"balance", destination), data);
    require(verifyProof(keccak256(address(this), "balance", destination), destinationProof));
    uint destinationBalance = uint(destinationProof[3]);

    uint amount = uint(getKeyProof(keccak256("amount"), data)[1]);

    require(senderBalance >= amount);

    //invalidate existing proofs
    //todo: what if an item goes to 0

    results = pushDel(senderProof, results);
    if (destinationBalance > 0) {
     results = pushDel(destinationProof, results);
    }

    //publish new proofs
    destinationBalance = destinationBalance + amount;
    senderBalance = senderBalance - amount;

    if(senderBalance > 0){
        results = pushAdd(bytes32(senderBalance), senderProof, results);
    }

    if(destinationBalance > 0){
        results = pushAdd(bytes32(destinationBalance), destinationProof, results);
    }

    return results;

}

/* things that need to be in the log
    inputDataHash: -> maybe put everything but signature here
        sender:
        gasPrice:
        maxGas:
        timeOut:
        contract:
        function:
        data:
        value:
        nonce:
    signatureInputDataHash: -> to ecrecover sender

    //value will need to be stored in escrow until the op can be proven to have run and a reciept generated

    //structure of data passed to a function

    [length][variableName][loghash][value][map][map][map][map][proof]...[proof]

    [length][path][nonce][type][value][proff]...[proof]


*/

event Transfer(bytes32 dataHash, bytes signature);

function transfer(bytes32 dataHash, bytes sig) public{
    Transfer(dataHash, sig);
}

 //utility function that can verify merkel proofs
//todo: can be optimized
//todo: move to library
function calcRoot(bytes32 path,  bytes32[] proof) constant public returns(bytes32 lastHash, uint logType, uint nonce, bytes32 proofPath, bytes32 value){
   for(uint thisProof = 0; thisProof < proof.length; thisProof++){
    if(thisProof == 0){
       //path
       require(path == proof[thisProof]);
       proofPath = path;
     } else if(thisProof == 2){
         nonce = uint(proof[thisProof]);
     } else if(thisProof == 1){
         //type
         logType = uint(proof[thisProof]);
         if(logType == 3){
             //null
             return;
         }
     } else if(thisProof == 3){
       value = proof[thisProof];
       lastHash = keccak256(path,logType, nonce, value);
     } else if(proof[thisProof] == 0x0){
        return;
     } else{
       if(proof[thisProof] == lastHash){
        if(proof[thisProof + 1] != 0x0) {
          lastHash = keccak256(lastHash, proof[thisProof + 1]);
        } else {
          lastHash = keccak256(lastHash);
        }

         thisProof++;
       } else {
         require(proof[thisProof + 1] == lastHash);
         lastHash = keccak256(proof[thisProof], lastHash);
         thisProof++;
       }
     }
    }
    return;
}

//utility function that can verify merkel proofs
//todo: can be optimized
//todo: move to library
function verifyProof(bytes32 path,  bytes32[] proof) constant public returns(bool){
   bytes32 lastHash;
   bytes32 emptyBytes;
   bytes32 value;
   uint nonce;
   uint logType;

   (lastHash, logType, nonce, path,  value) = calcRoot(path, proof);

   if(nonce == 3){
       return true;
   }

   for(uint thisLayer; thisLayer < bottomLayer.length; thisLayer++){
       if(bottomLayer[thisLayer] == lastHash){
           return true;
       }
   }

   for(thisLayer = 0; thisLayer < topLayer.length; thisLayer++){
       if(topLayer[thisLayer] == lastHash){
           return true;
       }
   }
   return false;
 }



}

   

The following code builds a little tree generator that helps manage the state of a tree and to produce Merkle proofs.  These proofs can probably be streamlined as I'm including derived hashes in the proofs for simplicity's sake.  Although since we are doing these virtually the size of the proofs only affects the bandwidth for collators, and the proofs aren't that big.

class DataLogTree
  constructor: ()->
    @web3Utils = require('web3-utils')
    @root = null
    @layers = []
    @layers.push([])
  addItem: (logType, nonce, path, value)=>
    @layers[0].push [logType, nonce, path, value]
  getVar: (key, value)=>
    map = []
    map.push(@web3Utils.padLeft(@web3Utils.toHex(3),64))
    map.push(key)
    map.push(value)
    return map
  getNullProof: (key)=>
    map = []
    map.push(@web3Utils.padLeft(5,64))
    map.push key
    map.push(@web3Utils.padLeft(@web3Utils.toHex(3),64))
    map.push(@web3Utils.padLeft(@web3Utils.toHex(0),64))
    map.push(@web3Utils.padLeft(@web3Utils.toHex(0),64))
    return map
  proofBytes: (items)=>
    bytes = "0x"
    items.map (item)=>

      item.map (subItem)=>
        bytes = bytes + subItem.slice(2)
        return
      return
    return bytes
  proofBytesArray: (myBytes)=>
    @web3Utils.hexToBytes(myBytes)
  parseLogs: (logsBytes)=>
    logsBytes = logsBytes.slice(2)
    position = 0
    logs = []
    while position < logsBytes.length
      logs.push [
        "0x" + logsBytes.substring(position,position + 64)
        "0x" + logsBytes.substring(position + 64,position + 128)
        "0x" + logsBytes.substring(position + 128,position + 192)
        "0x" + logsBytes.substring(position + 192,position + 256)
      ]
      position = position + 256
    return logs
  getProof: (type, nonce, path)=>
    map = []
    seek = null
    console.log type
    console.log nonce
    console.log path
    # 'looking for ' + key
    # to produce a proof we look through each layer from bottom to top looking for first the
    # passed in key and then the hash combination
    for thisLayer in [0...@layers.length]
      console.log 'seeking layer '+ thisLayer
      for thisItem in [0...@layers[thisLayer].length]
        console.log 'inspecting:' + thisItem
        console.log @layers[thisLayer][thisItem]
        if thisLayer is 0
          console.log 'in 0'
          if @layers[thisLayer][thisItem][0] is path and @layers[thisLayer][thisItem][1] is type and @layers[thisLayer][thisItem][2] is nonce
            console.log 'found 0'
            map.push @layers[thisLayer][thisItem][0]
            map.push @layers[thisLayer][thisItem][1]
            map.push @layers[thisLayer][thisItem][2]
            map.push @layers[thisLayer][thisItem][3]
            console.log map
            seek = @hasher map[0], map[1], map[2], map[3]
            console.log 'new seek is ' + seek
            break
        else
          # The found item will be either on the left or right hand side
          if @layers[thisLayer][thisItem][0] is seek
            # console.log 'found seek in position 0'
            # push the item onto the proof and find the next item
            map.push seek
            if @layers[thisLayer][thisItem][1]?
              map.push @layers[thisLayer][thisItem][1]
              seek = @hasher @layers[thisLayer][thisItem][0], @layers[thisLayer][thisItem][1]
            else
              map.push @web3Utils.padLeft(0,64)
              seek = @hasher @layers[thisLayer][thisItem][0]
            console.log 'new seek is ' + seek
            console.log map
            break
          if @layers[thisLayer][thisItem][1] is seek
            #console.log 'found seek in position 1'
            # push the item onto the proof and find the next item
            map.push @layers[thisLayer][thisItem][0]
            map.push seek
            seek = @hasher @layers[thisLayer][thisItem][0], @layers[thisLayer][thisItem][1]
            console.log 'new seek is ' + seek
            console.log map
            break
          if thisItem is @layers[thisLayer].length
            throw 'seek not found'
    if seek is @root
      map.unshift(@web3Utils.padLeft(@web3Utils.toHex(map.length + 1),64))
      return map
    else
      throw 'root not found'
  hasher:(val1, val2, val3, val4) =>
    if val4?
      hash = @web3Utils.soliditySha3({t:"bytes",v:val1},{t:"bytes",v:val2},{t:"bytes",v:val3},{t:"bytes",v:val4})
    else if val2?
      hash = @web3Utils.soliditySha3({t:"bytes",v:val1},{t:"bytes",v:val2})
    else
      hash = @web3Utils.soliditySha3({t:"bytes",v:val1})
    return hash
  buildTree: ()=>
    if @layers[1]?.length > 0
      @layers = [@layers[0]]
    console.log @layers[0].length
    pair = []
    currentLayer = 0
    console.log 'currentLayer:' + currentLayer
    console.log 'currentLayer Length:' + @layers[currentLayer].length

    if @layers[currentLayer].length is 1
      console.log @layers[currentLayer]
      hash = @hasher @layers[currentLayer][0][0], @layers[currentLayer][0][1], @layers[currentLayer][0][2], @layers[currentLayer][0][3]
      @root = hash
      console.log @root
      return

    while @layers[currentLayer]? and @layers[currentLayer].length > 1
      console.log 'in layer loop'
      console.log currentLayer
      console.log @layers[currentLayer]
      console.log 'end'

      @layers.push []
      console.log @layers
      #console.log @layers[currentLayer]

      for thisItem in @layers[currentLayer]
        console.log thisItem
        #console.log 'odd item' if thisItem.length != 2

        if currentLayer is 0
          console.log 'building 0 layer'
          hash = @hasher thisItem[0], thisItem[1], thisItem[2], thisItem[3]
          console.log hash
        else
          console.log 'building layer ' + currentLayer
          if thisItem[1]?
            hash = @hasher thisItem[0], thisItem[1]
          else
            hash = @hasher thisItem[0]

        #console.log hash
        pair.push hash
        console.log 'update pair'
        console.log pair.length

        if pair.length is 2
          console.log 'pushing hash'
          @layers[currentLayer + 1].push [pair[0],pair[1]]
          pair = []

      if pair.length is 1
        console.log 'pushing leftover hash'
        @layers[currentLayer + 1].push [pair[0]]
        pair = []
      console.log 'advancing layer'
      currentLayer = currentLayer + 1
      if currentLayer > 16
        throw new Error('yo')
    console.log 'done'
    console.log @layers

    @root = @hasher @layers[@layers.length - 1][0][0], @layers[@layers.length - 1][0][1]
    console.log @root


exports.DataLogTree = DataLogTree

    

Here is a truffle scenario that runs three transactions

console.log 'hello'
web3Utils = require('web3-utils')
VirtualToken =  artifacts.require('./VirtualToken.sol')
DataLogTree = require('../src/DataLogTree.js')



contract 'VirtualToken', (paccount)->
  owner = paccount[0]
  firstPayee = paccount[1]
  secondPayee = paccount[2]

  setUpNetwork = (options)=>
    return new Promise (resolve, reject)=>
      results = {}
      console.log 'creating virtual token'
      tree = new DataLogTree.DataLogTree()
      web3.eth.getBlockNumber (err, result)=>
        startBlock = result
        console.log startBlock
        token = await VirtualToken.new(from: owner)
        resolve
          token: token
          startBlock: startBlock
          tree: tree

  it "can set crete token", ->
    network = await setUpNetwork()
    logs = await new Promise (resolve, reject)=>
      network.token.DataLog({},{fromBlock: network.startBlock,toBlock:'latest'}).get (err, foundLogs)=>
        resolve foundLogs



    console.log logs

    genesisLog = null
    logs.map (o)->
      console.log o
      if o.event is 'DataLog'
        genesisLog = o
        #console.log [o.args.logType]#, web3Utils.padLeft(web3Utils.toHex(o.nonce),64), o.path, o.value]
        network.tree.addItem(o.args.path, web3Utils.padLeft(web3Utils.toHex(o.args.logType),64), web3Utils.padLeft(web3Utils.toHex(o.args.nonce),64), o.args.value)
        #console.log o.args

    console.log 'building tree'
    network.tree.buildTree()

    console.log network.tree.root

    console.log 'getting proof'
    proof = network.tree.getProof(web3Utils.padLeft(web3Utils.toHex(genesisLog.args.logType),64), web3Utils.padLeft(web3Utils.toHex(genesisLog.args.nonce),64), genesisLog.args.path)
    console.log proof

    expectedRoot = web3Utils.soliditySha3(proof[1],proof[2],proof[3],proof[4])

    assert.equal expectedRoot, network.tree.root

    txn = await network.token.publishCollation(expectedRoot, "0x0")

    transactionBytes = []
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("msg.sender"), web3Utils.padLeft(owner, 64))
    transactionBytes.push proof
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("amount"), web3Utils.padLeft(web3Utils.toHex(1000), 64))
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("destination"), web3Utils.padLeft(firstPayee,64))
    transactionBytes.push network.tree.getNullProof(web3Utils.soliditySha3(network.token.address,"balance", firstPayee))

    console.log transactionBytes
    myBytes = network.tree.proofBytes(transactionBytes)

    console.log myBytes

    console.log '["' + network.tree.proofBytesArray(myBytes).join('","') + '"]'

    txn = await network.token.vTransfer.call(myBytes)

    console.log ' trying second transaction ********'

    logs = network.tree.parseLogs txn

    console.log logs

    state1Tree = new DataLogTree.DataLogTree()


    for thisItem in logs
      state1Tree.addItem(thisItem[0], thisItem[1], thisItem[2], thisItem[3])

    console.log 'building state1Tree'

    state1Tree.buildTree()

    txn = await network.token.publishCollation(state1Tree.root, "0x0")

    console.log 'getting proof'
    proof = state1Tree.getProof(web3Utils.padLeft(web3Utils.toHex(1),64), web3Utils.padLeft(web3Utils.toHex(1),64), web3Utils.soliditySha3(network.token.address,"balance", firstPayee))
    console.log proof

    transactionBytes = []
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("msg.sender"), web3Utils.padLeft(firstPayee, 64))
    transactionBytes.push proof
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("amount"), web3Utils.padLeft(web3Utils.toHex(500), 64))
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("destination"), web3Utils.padLeft(secondPayee,64))
    transactionBytes.push network.tree.getNullProof(web3Utils.soliditySha3(network.token.address,"balance", secondPayee))

    console.log 'transaction bytes for second transaction'
    console.log transactionBytes

    myBytes = network.tree.proofBytes(transactionBytes)


    txn = await network.token.vTransfer.call(myBytes)

    console.log 'second transaction results'

    logs = network.tree.parseLogs txn

    console.log logs

    console.log ' trying third transaction ******************************************'

    state2Tree = new DataLogTree.DataLogTree()

    for thisItem in logs
      state2Tree.addItem(thisItem[0], thisItem[1], thisItem[2], thisItem[3])

    console.log state2Tree.layers[0]

    state2Tree.buildTree()

    txn = await network.token.publishCollation(state2Tree.root, "0x0")

    proof = state2Tree.getProof(web3Utils.padLeft(web3Utils.toHex(1),64), web3Utils.padLeft(web3Utils.toHex(1),64), web3Utils.soliditySha3(network.token.address,"balance", secondPayee))
    console.log proof

    secondProof = state1Tree.getProof(web3Utils.padLeft(web3Utils.toHex(1),64), web3Utils.padLeft(web3Utils.toHex(1),64), web3Utils.soliditySha3(network.token.address,"balance", owner))


    transactionBytes = []
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("msg.sender"), web3Utils.padLeft(secondPayee, 64))
    transactionBytes.push proof
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("amount"), web3Utils.padLeft(web3Utils.toHex(250), 64))
    transactionBytes.push network.tree.getVar(web3Utils.soliditySha3("destination"), web3Utils.padLeft(owner,64))
    transactionBytes.push secondProof

    myBytes = network.tree.proofBytes(transactionBytes)


    txn = await network.token.vTransfer.call(myBytes)

    logs = network.tree.parseLogs txn

    console.log logs

    assert.equal 1, 0, "hello"

   

Apologies for the coffeescript...it is 2.0 now so you get all the ES6 goodies.

So far only the red highlighted areas are working:

Scalable Stateless Contracts on Today's EVM Diagram step1.png

 

There are obviously a ton of areas that have big questions:

1.  Can we create a viable consensus amongst collators?
2.  How do we incentivize them?
3.  How to handle no ops?
4.  Have we really just put ethereum inside of ethereum and erased a lot of the checks that ethereum puts on run away gas costs?

I'm open to suggestions for 1-3.

For number four I think it is valuable to keep pushing this string.  There are certainly times where one would want the current EVM to do bigger things.  For example:  Using this I could write an air drop contract that loads up 1,000 balances for way less gas than it would currently cost...if I can get the collators to validate the transaction for me.

*The proof format here is [length, path, type(1=add, 2=del, 3=null), nonce, value, left leaf0, right leaf0.... left leafn, right leafn] where odd layers have an 0x0 on the end but the hash for those odd layers is calculated as the hash of just the left item.  Open to better suggestions.

DevLog 14 - Using a Catallax Trust to Pay Bug Bounties

Fresh off DevCon 3 we have our first bug submitted on the Catallax Trust.  Github user NickErrant pointed out in this github issue that a franchisee could lock up funds by pointing their payment address to a contract that throws on the fall back function.

Generally you can trust that people that want to get paid by a contract won’t do this, but in the case of a Catallax Trust the franchisee could be disgruntled about their payout or choose to protest for some other reason and lock up the contract.

The reason this can happen is that if address.transfer function fails then the function will throw.  Because our withdraw() function pays out to the franchisee if they exist they can block the payment.  The simple fix (and the one we’ve taken at the moment) is to use address.send instead.  If this fails it just returns false and the rest of the function continues to function.

A better solution is to use the withdraw pattern and just set the money aside for the franchisee to come get later.  This requires another storage variable in the contract so it has some cost.

We haven’t deployed the factory to produce this new contract yet so don’t set up a franchise contract until we do so. If you are interested in setting one of these up, please reach out to us and we will work with you to get everything setup correctly.

Nick raised a good point when reporting the bug that bug bounty contracts where you just try to steal the funds have pretty poor incentives for bug hunters.  As a result, we’ve made Nick the beneficiary of the bug bounty contract.  On 11/16 he’ll be able to call withdraw and get out 1/24th of the current balance.

We will leave Nick as the beneficiary until we get our next valid bug report.  At that time, provided the bug is as serious as Nick’s, we will unlock the beneficiary and transfer it to the new bug hunter 36 days later.

Of course, if you'd like to try to steal the money out of the trust you can do that too.

If you’d like to use a Catallax Trust for one of your own bug bounties please reach out to us and we can help you set it up.  The trust supports ETH and ERC20 tokens.

You can find the source code for the contracts here: https://github.com/skilesare/catallaxtrust

Pull down the repo and load them up in remix to interact with the contracts.

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

You can discuss this article and more at our reddit page r/Catallax.

DevLog 13 - $100 Catallax Trust Bug Bounty

The plan was to go with a push out to Ropsten today with the web app up and running.  I’ve had a muted response to the application so far so I’m going to try something different.  I really need some more eyes on this contract before I pour a bunch of dev cycles at the dapp.  So I’ve thrown all caution to the wind and deployed the contracts to mainnet.

I’m created a Catallax Trust Custodian(0x1ed1ee3d6cf25754046e8769f4f2feff57ede7a3), Factory(0x6824457c6c5f711b71dc28c804c6ca767fc84046), Trust Storage(0x79244a86de9b499b03d8c0afe29460d029c5e7a6), and Catallax Trust(0xb8c7842b4451c440f14f0ccfa7cc4bb9734e5df5).  The trust is a two year trust that pays out $30,000 a month.  I’ve loaded it up with $100 worth of ETH.  Please do your best to pull this eth out of the trust.  If you can break the trust please let me know what you did to break it at austin at catallax dot com.

You can find the source code for the contracts here: https://github.com/skilesare/catallaxtrust

Pull down the repo and load them up in remix to interact with the contracts.

I have a bit of time off this week and I’m not sure if I’ll make much progress on the dapp or not.  Some positive feedback and some potential customers might light a fire under me to make the contracts easier to interact with.

If you would like to start a trust right now you can do so by calling the Custodian.CreateTrust function,  Funding the created trust, and then calling the Trust.StartTrust function.

If you’d like to start one but want to see more scrutiny on the contract feel free to send ETH to the Trust address at 0xb8c7842b4451c440f14f0ccfa7cc4bb9734e5df5 to increase the bounty.

Have fun exploring the contracts!  Post questions and issues in our github or reddit here.

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

You can discuss this article and more at our reddit page r/Catallax.

DevLog 12 - Catallax Trust Contract Code Release

I’m really excited to release our code for the Catallax Trust today.  It has been a long time coming and it will be nice to get some new eyes on the code.

If this is your first time reading about the Catallax Trust you can read the white paper here.  A Catallax Trust is a contract that will hold crypto and pay it out in the future in fiat-based chunks based on the exchange rate at the time of the withdrawal.  I can be used for employment contracts, donating to a charity, securing ICO funds, or simply helping you HODL with a budget.

We have had one code review done and implemented a number of improvements identified in that code review.  Of course, we hope that the contracts are production ready, but if you think otherwise, please let us know by sending the issue to bugs@catallax.com or filing an issue on the github repo:   https://github.com/skilesare/catallaxtrust

The Catallax Trust is made up of the following main contracts:

FiatTrustCustodian.sol - This is the custodian contract that oversees the creation of new trusts and tracks the fiat to crypto conversion rates for supported crypto / fiat pairs.

FiatTrustFactory.sol - This is the factory contract that the custodian uses to create new trusts.  We can swap this out if we create a better trust without having to republish the custodian.

FiatTrust.sol - This contract is the core trust. Users will deposit their ETH and / or ERC20 tokens in these contracts and be able to withdraw from them over time.

The following support contracts are included as well:

DateTime.sol - a date time library

SafeMath.sol - safe arithmetic functions

ERC20.sol - interacting with tokens

TrustStorage.sol - we have to store the historical history of crypto / fiat exchange rates. This is held in a storage contract so that we can easily move the history to upgraded custodians if necessary.

iLicensor.sol - a stub contract for future governance of the custodian

The general process for setting up the contracts follows the following process(this is the code from our test suite):

prepEnvironment = (custodianOwner)->
 return new Promise (resolve, reject)->
   custodian = null
   factory = null
   token = null
   storage = null
   #Create the custodian
   FiatTrustCustodian.new(from: custodianOwner).then (instance)->
     custodian = instance
     #create the factory
     FiatTrustFactory.new(custodian.address, from: custodianOwner)
   .then (instance)->
     console.log 'new factory'
     factory = instance
     #create a datetime library(this has been published previously on mainnet so you can use one of those)
     DateTime.new(from: custodianOwner)
   .then (instance)->
     console.log 'new DateTime'
     #set the date time library
     custodian.SetDateTimeLibrary(instance.address, from: custodianOwner)
   .then (result)->
     console.log 'dt set'
     #set the factory location
     custodian.SetFactory(factory.address, from:custodianOwner)
   .then (result)->
     console.log 'factory set'
     #Create an ERC20 token to test with
     HumanStandardToken.new(tokenStartBalance,"token",0,'tkn', from: custodianOwner)
   .then (instance)->
     console.log 'new token'
     token = instance
     #create a new storage contract
     TokenStorage.new(from: custodianOwner)
   .then (instance)->
     console.log 'new storage'
     storage = instance
     #set the storage contract
     custodian.SetStorage(storage.address, from: custodianOwner)
   .then (instance) ->
     console.log 'storage set'
     #update the owner of the storage to include the custodian contract
     storage.UpdateOwner(custodian.address, true, from: custodianOwner)
   .then (instance)->
     console.log 'first conversion set'
     #set an old conversion for ETH to USD
     custodian.SetConversion(ethTokenAddress, usdCurrencybytes, 1989,1, 1, web3.toWei(0.01,"ether"),1, from: custodianOwner)
   .then (instance)->
     console.log 'conversion set'
     #set an old conversion for ERC20 token to USD
     custodian.SetConversion(token.address, usdCurrencybytes, 1989,1, 1, web3.toWei(0.01,"ether"),1, from: custodianOwner)
   .then (instance)->
     console.log 'max fee set'
     #set the max fee
     custodian.SetMaxFee(usdCurrencybytes, 50, from: custodianOwner)
   .then (instance)->
     console.log 'origination fee set set'
     #set the origination fee
     custodian.SetOriginationFee(usdCurrencybytes, 25, from: custodianOwner)
   .then ->
     resolve
       custodian: custodian
       token: token

Once your custodian is configured you need to create, fund, and start your trust.  The following code will do that and then make time pass so you can do your first withdrawal:

custodian.CreateTrust(ethTokenAddress, usdCurrencybytes, 12, 1, {from: accounts[0]})
   .then (txn)->
     trustAddress = null
     txn.logs.map (o)->
       if o.event is 'TrustCreated'
         console.log 'found new Trust at' + o.args.location
         i = FiatTrust.at(o.args.location)
         trustAddress = o.args.location
     console.log 'have instance'
     #fund the wallet
     web3.eth.sendTransaction({ from: accounts[1], to: i.address, value: web3.toWei(0.44,"ether") })
   .then (result)->
     #Start the Trust
     i.StartTrust(from:accounts[0])
   .then (result)->
     console.log result
     console.log 'sending ether'
     #this function advances time in our test client by 34 days
     return new Promise (tResolve, tReject)->
       web3.currentProvider.sendAsync
         jsonrpc: "2.0",
         method: "evm_increaseTime",
         params: [86400 * 34],  # 86400 seconds in a day
         id: new Date().getTime()
       , (err)->
         tResolve true
   .then (result)->
     #calculate the next withdraw date
     i.NextWithdraw()
   .then (result)->
     console.log 'next ' + result
     nextPayout = result.toNumber()
     aDate = nextPayout * 1000
     aDate = moment.utc(new Date(aDate))
     console.log aDate
     console.log 'conversion set for ' + aDate.year() + (aDate.month() + 1) + aDate.date()
     #set the conversion for that tdate
     custodian.SetConversion(ethTokenAddress, usdCurrencybytes, aDate.year(), aDate.month() + 1, aDate.date(), web3.toWei(0.01,"ether"),1)
   .then (result)->#call the withdraw fucntion.  0.1 eth shold move from the contract to account[0]
     console.log 'conversion set'
     aDate = nextPayout * 1000
     aDate = moment.utc(new Date(aDate))
     console.log aDate
     #check the current balance
     web3.eth.getBalance(custodian.address)
   .then (result)->
     console.log result
     startCustodianBalance = result.toNumber()
     assert.equal 250000000000000000, startCustodianBalance, 'custodian has ether it shouldnt'
     startBalance = web3.eth.getBalance(accounts[0])
     console.log 'Start Balance' + startBalance
     #withdraw from the trust
     i.Withdraw(from: accounts[0])
   .then (result)->
     console.log result
     web3.eth.getBalance(i.address)
   .then (result)->
     console.log 'withdrawl:' + result
     assert.equal result.toNumber(), parseInt(web3.toWei(0.18,"ether")) - parseInt(web3.toWei(0.01,"ether")) * 0.005, 'withdraw wasnt right'
     web3.eth.getBalance(accounts[0])
   .then (result)->
     console.log result
     #since payout is 0 eth per usd we multiply fiatpayout 1
     #we only test .9 eth and 1.1 because gas costs weigh in
     # so much gas cost
     #96 413397700000000000
     #96 502336500000000000
     #
     console.log 'eth increased' + (result.toNumber() - startBalance.toNumber())
     assert.equal result.toNumber() > startBalance.toNumber(), true, 'eth didnt transfer'
     assert.equal result.toNumber() < startBalance.toNumber() + parseInt(web3.toWei(0.01,"ether")), true, 'too much eth transfered'
     web3.eth.getBalance(custodian.address)
   .then (result)->
     console.log 'fee paid' + result
     assert.equal result.toNumber(), 250000000000000000 + parseInt(web3.toWei(0.01,"ether")) * 0.005, 'Fee wasnt paid'

Have fun exploring the contracts!  Post questions and issues in our github or reddit here.

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

You can discuss this article and more at our reddit page r/Catallax.

DevLog 11 - Catallax Trust White Paper and Release Schedule

I’ve spent the last three weeks working on the Web App for our Project MCD.  I have the functionality there and am now onto the design.  I’ve tried to simplify things along the way so that I can get things to release.

Now that all the functionality is in place I’m happy to announce the release schedule!

Project MCD is actually a project called the Catallax Trust.  The Catallax Trust is a fiat trust that will let you lock up crypto and have it paid out over time based on the price of crypto assets on the date of payout.

For example:  Put 100 ETH into a contract and have it pay out $1,000 a month for 3 years.  If the price of ETH goes up less ETH will come out each month.  If it goes down you will get more ETH, but your trust may run out of money before the 3 years is up.

These trusts are good for employment contracts, giving to charities, keeping your self-disciplined and HODLing during the fork to Metropolis and the inevitable volatility that will follow.

I’m releasing the whitepaper today, code soon, test net after that and hoping to be live by the time we get to DevCon 3.

Schedule:

October 2nd:  White Paper 

October 9th:  Contract Code Release

October 16th:  TestNet Release

October 23rd: Live

Please head over to this thread on our Reddit to pick the white paper apart and ask questions.

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

You can discuss this article and more at our reddit page r/Catallax.

DevLog 10 - HarveyCoin - Decentralized Disaster Relief

***Read Past the intro to hear about a set of Etherum contracts that let you donate your existing ETH today to people that need help and get it all back in the future as the disaster is mitigated.***

Catchup:

Another month has gone by since the last Catallax DevLog update and it has been a busy month.  Work on project MCD continues and I’m still hoping to hit October 1st unless I get derailed by the project I’ll be discussing today.  The most interesting news is that we participated in the dAppathon put on by BCG DV and Dvolution in LA. We launched a prototype for a service called Lannister (http://lannister.tech) that lets you collateralize any existing or future ownable ethereum service.  I’ll publish a dev log on this in the future but we have more important things to talk about today. 

Now for today’s news and request for help: 

I’m from Houston and I haven’t been home for 2 ½  weeks.  I’ve seen pictures and I’ve heard stories. We’re headed back home now to a dry house with our shoulders filled with survivor’s guilt.  I’m a computer programmer.  I’m not a jump in a boat and pull people out of houses kind of guy.  I know people like that and I’m amazed at their service. What can I do?  How can I help?

I’ve started something and I need your help.  One of Catallax’s core concepts is the idea that decaying money can be used to rapidly rebuild a crumbled economy.  This was tried in Austria in the 1930s and was so successful that it threatened the central bank’s authority to issue currency and was shut down. See the Worgl experiment.  Ethereum lets us try it again very easily.

We have very big plan on how to implement this in a global way that fundamentally realigns incentives in the economy.  We are working on it, but we need help now.  Houston is hurting and Irma is hurling toward Florida and about to create another disaster area.

So here is what I have so far.  I call it HarveyCoin.  Open to better names.

HarveyCoin

HarveyCoin allows you to donate ETH to people who need it today and get it back over the course of the future in a guaranteed way. It is a glorified HODL strategy that helps people get their homes back while you maintain your position in ETH that you plan on cashing in far in the future.

There are four use cases: 

  1. Donate.  Donate your ETH today by creating a HarveyWallet and minting new HarveyCoin.  There is a 1:1 relationship between HarveyCoin and ETH.  Your ETH will be given out to people in need over the next 5 years and will decay back to you over that time.  So if you donate 5 ETH today you’ll be able to reclaim 1 ETH in a year, 1 ETH the year after that, etc.

  2. Become a Service Provider.  Create a HarveyWallet and begin accepting HarveyCoin for your services today.  The ETH you make by selling your services will decay at a stiff ~20% rate, but per day this is fairly low if you work with your fellow vendors and employees to get them onto the system as well and can turn over your HarveyCoin quickly.

  3. Get help.  Apply to the custodian to get disaster relief funds in the form of HarveyCoin in your HarveyWallet.  You’ll be free to spend the HarveyCoin with any approved vendors or other citizen that has a HarveyWallet.

  4. Exchange ETH for HarveyCoin.  Exchange cash or ETH to get more HarveyCoin.  This lets people who are dead ends in the ecosystem get value out the system for their HarveyCoin.  If the system is successful this should maintain as close of a 1:1 relationship between HarveyCoin and ETH as possible.  This gives us a good barometer of the system's success.

If you have ETH today that you plan on holding for a long time your only downside is liquidity. HarveyCoin helps you resist fear, uncertainty, and doubt while building the utility case for Ethereum. 

There is no profit built in for the custodian in the initial contracts.  The custodian can issue itself some HarveyCoin for administrative purposes, but I’m hoping this can be kept to a bare minimum and will keep the custodian true to making sure that HarveyCoin is as valuable as possible as it is the only funds that the custodian will be able to get access to.

You can find the command center for this project on r/Catallax here.  Please hop over and find a way to help.

Things we need:

  1. The initial contracts need to be vetted and can be found here.  I’m hoping to build some truffle tests in the near future as real life allows.  I wrote these quickly and they need some serious TLC before going live.
  2. Wallets for iOS and Android that make it dead simple to trade HarveyCoin.  I know react-native has some issues with implementing web3, but maybe there is a cordova/phonegap solution that can be put together quickly.
  3. Suggestions for the governance structure.  Right now the custodian just has an owner, but we can easily set that to a multisig wallet or some other governance structure.  All suggestions welcome here. I don't have the talent, training, or bandwidth to run a significant disaster recovery effort all by myself.
  4. A web app that directs the 4 use cases to relevant web apps that can capture info needed to approve wallets.
  5. Someone to write the exchange contracts.

I’ll quickly go through the basics of the contract:

HarveyCustodian

Members:

uint drain - holds decayed funds available to be claimed by minters

address treasury - the treasury wallet that minted funds go into for dispersal.  Also a HarveyWallet and subject to decay.

uint totalMinted - the total donated

uint totalDrained - the total reclaimed

address owner - owner of the custodian

mapping(address => bool) validWallets - allows the custodian to authorize HarveyWallets to participate in the system.

address[] wallets - a list of wallets that have participated with the system for iteration.

Functions:

setOwner - sets the owner of the custodian

setTreasury - sets the treasury wallet

mint - forwards funds to the treasury

unMint - allows minters to claim funds out of the drain

sendDecay - deposits decay with the custodian

setValidWallet - sets the validity of the wallet

calcVested - calcs the % vested over a 5 year period of a donation

availableToRedeem - calcs the amount available to be withdrawn given a minted amount, redeemed amount, and startTime

HarveyWallet

Members:

mapping(address => bool) owner - holds the owner status of the wallet.  Allows for multiple owners

address custodian - the custodian overseeing the wallet

uint lastCatchup - records the last time the wallet paid the decay fee

uint mintStart - records the time the wallet minted(you can only mint once)

uint minted - amount minted

uint redeemed - amount redeemed already

Functions:

setOwner - sets the address and validity of an owner

pay - pays an amount to another valid HarveyWallet.

calcCatchup - calculates how much this wallet owes in decay fees.

catchup - pays the decay fee to the custodian.

mint - donates ETH to the treasury that can be claimed back as time moves forward.

unMint - claims back ETH from the drain

Please hop over to r/Catallax to discuss more. 

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

You can discuss this article and more at our reddit page r/Catallax.

 

Dev Log 9 - New OpCodes and Schedule Update

It has been about a month since the last update and the reason is that I’ve been coding like crazy.

I’ve discovered some cool stuff coming in metropolis that will make Project Sigmund a much better and more useful project.  The opCodes are returndatasize and returndatacopy.  I’m going to have to start learning some assembly for this so if you have some good suggestions on where to gets started with assembly, let me know.  I want to do something like this:

bytes results;

bool testresult = aContract.call(sig, _1);

if(result == false) throw;

assembly {

// retrieve the size of the code, this needs assembly

let size := returndatasize()

returndatacopy(results,0, size)

}

return results;

As a result of this discovery, I’m moving Project Sigmund Back to after Metropolis comes out.  I’ve been very busy with Project MCD.  As of today, I have the contracts feature complete and there is just a bit of refactoring to come.  Then, on to the web app.  I’m don’t think I’m going to hit my original August 15th deadline, and I now would like to have it mostly ready to go by August 24th when I’m heading out to LA to participate in the Dappathon. If you are going to be there, please let me know.  I'd love to sync up and I'm still looking for a team to participate with.

The coding on these contracts is giving more perspective and I’m pretty sure that Project BMP is a no go because they just take too long to build and I really like the direction I’m going for a full featured Catallaxian set of contracts in Project Wall-e so I may just jump into those after MCD goes live.

I’ve also got a good bit of legal advice on Project CANb.  As you know the SEC ruled on the DAO and that is really steering me away from a token based project.  We’ll see if there is a good solution or not, but i’m still hoping to get the basic white paper ready to shop around at DevCon. I may have to leave the idea on the cutting room floor, but hope remains.

Here is the updated schedule

  • Project Sigmund - August 15th, 2017(delayed to after Metropolis)

  • Project MCD - October 1st, 2017(Expected August 24th)

  • Project BMP - November 1st, 2017 (DevCon3) (In jepordy of being cancled)

  • Project CANb - April 1st, 2018(Facing legal hurdles)

  • Project WALL-e - August 1st, 2018(May get moved up)

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

You can discuss this article and more at our reddit page r/Catallax.

Dev Log 8 - Project Timeline

Today is a short update.  The last week and a half have been pretty full of 3-hour feeding cycles trying to get my new son up from 6lbs. to 7lbs.  We hit 7 this morning.  Great Success.  As a result, this week has been light on the coding side of things as the day job has been getting almost all available cycles.

The coding that I have been doing has been scoping out the framework for our first "real-world product."  Since it is all theoretical so far I'm not quite ready to show any of it off.  What I have done is put together a very aggressive timeline of the Catallax project.  I'll offer it here without many details.  Feel free to hold my feet to the fire.  I'm sure we'll see some slippage, but if I don't set some hard goals I'll just stay in the learning phase forever.

  • Project Sigmund - August 15th, 2017
  • Project MCD - October 1st, 2017
  • Project BMP - November 1st, 2017 (DevCon3)
  • Project CANb - April 1st, 2018
  • Project WALL-e - August 1st, 2018

This is a pretty aggressive schedule and has me building 3 production level dApps by DevCon.  I kind of doubt the 3rd one will be ready, but I hope that the first two will be.  Sigmund and MCD do not have any kind of ICO component.  I'm hoping that they will be solid, real-world products that can provide some revenue for the remaining development.

Project BMP is a really exciting experiment in Catallaxian economics and I hope I can have some of it ready for DevCon.

CANb and WALL-e are big, epic projects that require a lot of jumping through regulation hoops and infrastructure creation.  I've given them a little more runway.

Next week I hope to have some lessons learned from my develpment on Sigmund.

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

You can discuss this article and more at our reddit page r/Catallax.

Storage Contracts, Mappings, and ETL - Dev Log 8

Weekly Updates:

  1. Did I mention I was a father again? Not a whole lot of coding this week as I’m trapped in the whirlpool of 3 hour feeding cycles.  Never fear though we do explore something of value today.
  2. My book Immortality is up to #123 in the Kindle Store > Kindle eBooks > Nonfiction > Politics & Social Sciences > Philosophy > Metaphysics category on the kindle store.  Watch out world.
  3. I purchased my various tickets to DevCon3.  Look out Mexico, Here I come!

The Code

Today’s post isn’t very long but I think it will help answer some questions for others who have been confused by the storage system in Ethereum.

There are a number of good pieces of info out there that talk about how you should handle storage for more complicated contracts.  The basic idea is that you create a storage contract to hold all of your data so that if your master contract needs to be replaced you can just deploy a new contract and point the storage to a new place.  Here are a few articles on how some of this works:

https://blog.colony.io/writing-upgradeable-contracts-in-solidity-6743f0eecc88
https://ethereum.stackexchange.com/questions/13167/are-there-well-solved-and-simple-storage-patterns-for-solidity
https://ethereum.stackexchange.com/questions/15930/pushing-pulling-data-from-data-storage-contract
http://solidity.readthedocs.io/en/develop/miscellaneous.html

The colony article is really interesting and they end up setting a place for all different kinds of data.  As it is difficult to predict what kinds of data you are going to have I thought it would be interesting to think about how to make this more generic.  This gets especially difficult when you start doing mappings of mappings.   This is going to be important for our decaying tokens as we are going to need to keep track of transaction inputs in a mapping(address => mapping(address => amount)) such that we can get data you like prefs[reciver][payer]

My little example looks at allocating ERC20 allowances and how to store those:

contract MapTest {

    mapping(address => mapping(address => uint256)) public approvals;
    mapping(bytes32 => uint256) public approvalsKeccak256;
    mapping(bytes32 => uint256) public genericStore;

    function MapTest(){

    }

    function AddAllowanceMap(address sender, address recipient, uint256 amount){
        approvals[sender][recipient] = amount;
    }

    function AddAllowanceKeccak256(address sender, address recipient, uint256 amount){
        approvalsKeccak256[keccak256(sender,recipient)] = amount;
    }

    function AddAllowanceGeneric(address sender, address recipient,uint256 amount){
        genericStore[keccak256('allowance', sender, recipient)] = amount;
    }

}

If you load this up into Remix and run each function with "0x1","0x3",100 you will see that the gas cost is just about the same for each instance.

This is pretty powerful as we can now keep all of our mappings in one place.  This violates all kinds of separations of concerns and may be a bad idea, but it will make the code in our stage contract much cleaner.  So if we are going to violate the separation of concerns, let's go all the way and build a generic storage contract that can be ETLed if we need to copy data.

Here is the basic generic storage contract.

contract StorageTest {

    //keep track of if we have seen this key before or not
    mapping(bytes32 => bool) public genericStoreExists;

    //a place to put our data
    mapping(bytes32 => bytes32) public genericStore;

    //a place to keep track of our keys.  Out of order...but still, we
    //have them
    bytes32[] public genericIterator;

    //keep track of the number of keys we are storing
    uint256 public genericCount;

    function StorageTest(){

    }


    //store our data
    // We assume that your variable name has been convered to 
    // bytes32.  You can do this via a keccak or converting string to bytes
    // We assume you have keccaked your variable path
    // We assume that all values are cast to bytes32
    function PutValue(bytes32 _dataGroup, bytes32 _kecKey, bytes32 _value){

        bytes32 key = keccak256(_dataGroup, _kecKey);
        return PutValueRaw(key, _value);
    }

    function PutValueRaw(bytes32 key, bytes32 _value){

        genericStore[key] = _value;
        if(genericStoreExists[key] == false){
            genericStoreExists[key] = true;
            genericIterator.push(key);
            genericCount = genericCount + 1;
        }
    }


    //get the raw bytes
    function getBytes(bytes32 _dataGroup, bytes32 _kecKey) constant returns(bytes32){
        bytes32 key = keccak256(_dataGroup, _kecKey);
        return genericStore[key];
    }

    //get integers
    function getInt(bytes32 _dataGroup, bytes32 _kecKey) constant returns(uint256){
        bytes32 key = keccak256(_dataGroup, _kecKey);
        return uint256(genericStore[key]);
    }

    //get addresses
    function getAddress(bytes32 _dataGroup, bytes32 _kecKey) constant returns(address){
        bytes32 key = keccak256(_dataGroup, _kecKey);
        return address(genericStore[key]);
    }

    //get bools
    function getBool(bytes32 _dataGroup, bytes32 _kecKey) constant returns(bool){
        bytes32 key = keccak256(_dataGroup, _kecKey);
        bytes32 value = genericStore[key];
        if(value == 0x0){
            return false;
        }
        else{
            return true;
        }

    }

    //not sure about arrays
    //not sure about enums
}

There is more work to do here, but the basics are here.  Try putting in some data by creating a contract in remix and then submitting PutValue with “0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000001",”"0x0000000000000000000000000000000000000000000000000000000000000014".  

 

Once that is complete, call getInt with “0x0000000000000000000000000000000000000000000000000000000000000001","0x0000000000000000000000000000000000000000000000000000000000000001" and you should get out 20.  It works!

 

We can also ETL(Extract, Transform, Load) this contract.  Here I’ve built a small data copy contract that will move data from this storage to another storage contract of the same type:

contract StorageCopy {

    StorageTest public Source;
    StorageTest public Target;
    uint256 public CurrentItem;


    function StorageCopy(address _source, address _target){
        Source = StorageTest(_source);
        Target = StorageTest(_target);
    }

    function DoCopy() returns (uint256){
        uint256 SourceCount = Source.genericCount();
        for(uint256 i = CurrentItem; i < SourceCount; i++){
            Target.PutValueRaw(Source.genericIterator(i), 
                Source.genericStore(Source.genericIterator(i)));

            //keep track of where we are in the process
            CurrentItem = i;
            //todo:  scheme to check remaining gas and bail if gas gets too low
            //next call will start with Current item

        }
    }

}

Create two StorageTest contracts in Remix.  Call PutValue on the first contract with "0x1","0x1","0x0000000000000000000000000000000000000000000000000000000000000001" and "0x2","0x2","0x0000000000000000000000000000000000000000000000000000000000000002".  Now create a StorageCopy contract and pass in the addresses of your two created cotracts like this:  "0x0971b5d216af52c411c9016bbc63665b4e6f2542","0xde6a66562c299052b1cfd24abc1dc639d429e1d6" where the addresses are what Remix generated for you.  Call the DoCopy Function and the data should copy over.

You should now be able to call getInt with "0x2","0x2" on the second StorageTest contract and get out the 2 value you put in for that dataGroup / mapping address pair.  It works!

 

There is a lot left undone here.  You’d want to secure the data contract.  You also are going to have to deal with gas limits as the comments in the comments say.  There are some data types that I’m not sure how to handle yet.  Arrays could be particularly difficult.  You might be able to save a ton of gas by writing some native assembly in here as well.  All in all, though, this is a decent footprint for a basic storage contract.

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality (Purchase of a physical or kindle copy helps support this project).

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0

If you would like more code articles like this please consider becoming a patron on patreon.

Testing With Time - Dev Log 7

Weekly Update

  1. I’m a Dad again(for the fourth time).  Weston James was born on Tuesday evening June the 20th.  Mom and baby are healthy.  Big Bro, Big Sis, and Big Sis 2 are super excited, and Dad is really proud.
  2. I guess I’m a professional solidity developer as someone paid me actual money(well ETH) for the contract we are discussing today.  Not a lot, but enough be significant.
  3. Work continues on the whitepaper for the first service offering that Catallax will be trying to launch.  Lawyers, accountants, legalese, etc., etc.

Today we are going to do some testing with time.  The is going to be very important once we start testing our demurrage code.  In our decaying token we used passing block numbers to calculate our decay periods.  In the example today we are going to use the actual time system in Ethereum.  This system isn’t an exact science since we are somewhat dependent on the Miners to give a truthful timestamp on each block.  You would not use this system if you were doing calculations on short time scales because a miner could attempt to cheat your contract.  Our example today uses month long periods so we should be fine using time.
 
The contract we are going to develop is called a DisciplineWallet.  Do you have lambo money from buying into the ethereum crowdsale two years ago?  Afraid you’re going to get spooked and sell?  Use the DisciplineWallet and restrict access to your ETH on a monthly basis.
 
When we first started talking about this contract the client wanted the payout to be consistent in USD.  We went back and forth and finally decided that we needed to do it in ETH because there isn’t really a good USD to ETH Oracle that is reliable over long periods of time.  If you know of one, please let me know and I’ll try to update this contract to do USD instead.
 
Our contract will do the following:

  • Be initialized with a term in months and an amount of ETH to pay out each month
  • Take Deposits in ETH
  • Expose a Withdraw function that can be called once a month to send the specified amount of ETH to the owner of the contract
  • Let us change the owner of the contract
  • Withdraw all the remaining ETH to a specified address once the term has run its course
  • Give us a safety function to move coins out of the contact if we accidentally send ERC20 tokens to the contract
  • Use the fallback function as a deposit function
  • Tell us when the next withdrawal is available

Once we write up our contract we are going to want to test it.  To do this we are going to need a blockchain environment that will let us make time go by quickly. Fortunately, the testrpc that is packaged with the truffle dev environment lets us do this with the following call:

web3.currentProvider.sendAsync({
       jsonrpc: "2.0",
       method: "evm_increaseTime",
       params: [86400 * 34],
       id: new Date().getTime()
     }


In the above example, we are moving the clock forward 34 days(86400 seconds in a day).  Our tests will do withdrawals and then move the clock forward and do withdrawals again.
 
Our contract will need to pass the following tests:
 
    ✓ should Deploy a wallet with owner (91ms)
    ✓ should be off when it starts and has no ether (69ms)
    ✓ should have a function Period that returns the length of a month (102ms)
    ✓ should have a function NextWithdraw that returns the first withdraw time (94ms)
    ✓ should turn on when sent ether (775ms)
    ✓ should fail if constructor sent ether
    ✓ should allow withdraw after 1 month and ether goes to owner (750ms)
    ✓ should fail on instant withdraw (255ms)
    ✓ should not allow more than term number of withdraws (955ms)
    ✓ should not allow withdraw if some time has passed, but not enough (382ms)
    ✓ should allow withdraw all after term is over (3389ms)
    ✓ should fail if withdrawAll is called before term is over (388ms)
    ✓ should reject payment if payout term is expired (2778ms)
    ✓ should deposit using deposit function (245ms)
    ✓ should allow for transfer of owner (1529ms)
    ✓ should reduce payout if payout would drain account before the end of the term (679ms)

The code and truffle set up can be found on GitHub here. 
Here is our contact.

pragma solidity ^0.4.11;

contract DisciplineWallet {

  /**
  * @dev term is the number of months that a wallet will pay out
  */
  uint16 public term;


  /**
  * @dev currentTerm tracks the number of withdraws that have occured
  */
  uint16 public currentTerm;

  /**
  * @dev contractStart stores the timestamp(number of seconds since 1/1/1970) that the contract was created
  */
  uint public contractStart;

  /**
  * @dev payout is the number of wei(smallest unit of ETH) to pay out
  */
  uint public payout;


  /**
  * @dev bActive is tracks if the wallet is open for business or not
  */
  bool public bActive;
  address public owner;

  /**
   * @dev Throws if called by any account other than the owner.
   */
  modifier onlyOwner() {
    if (msg.sender != owner) {
      throw;
    }
    _;
  }

  /**
   * @dev constructor
   * @param termLength number of months that the contract will be active
   * @param weiOutputPerPeriod of wei to pay out
   */
  function DisciplineWallet(uint16 termLength, uint weiOutputPerPeriod) {

    if(termLength < 1) throw; //term cant be 0
    if(weiOutputPerPeriod < 1) throw; //we cant pay out 0

    //the owner will start as the address creating the contract
    owner = msg.sender;

    //set the term
    term = termLength;

    //record the time of the start contract
    contractStart = now;

    //start with the current term as 1
    currentTerm = 1;

    //set the payout per period
    payout = weiOutputPerPeriod;
  }


  /**
   * @dev calculates the length of a month
   */
  function Period() constant returns (uint32 lengthOfMonth){
    //put this in a constant function so that we don't have to use storage
    //execution takes 302 gas which means calculation is more efficent in any contract under 66 months.
    //not sure about deployment costs
    //the period is about 30.66 days so that leap year is taken into account every 4 years.
    return (1 years / 12) + (1 days / 4);
  }

  /**
   * @dev tells us what date we can call the withdraw function
   */
  function NextWithdraw() constant returns(uint secondsAfter1970){
    return contractStart + (Period() * currentTerm);
  }

  /**
   * @dev the fallback function will make a deposit
   */
  function () payable{
    Deposit();
  }

  /**
   * @dev Put ETH into the contract, also called by the fallback function
   */
  function Deposit() payable {
    //don't accept ether if the term is over
    if(currentTerm > term) throw;
    if(bActive == false){
      //first time we get ether, turn on the contract
      bActive = true;
    }
  }

  /**
   * @dev Withdraw will send the payout to the owner if enough time has passed
   */
  function Withdraw() onlyOwner returns(bool ok){
    if(currentTerm <= term && now > NextWithdraw()){

      //payout may not be more than balance / term or the account has been underfunded
      //if it is then use the lower calculatio
      if(payout < (this.balance / (term - currentTerm + 1))){
        owner.transfer(payout);
      }
      else{
        owner.transfer(this.balance / term);
      }

      currentTerm = currentTerm + 1;

      if (currentTerm > term){
        bActive = false;
      }

      return true;
    } else{
      throw;
    }

  }


  /**
   * @dev once all terms have expired you can move any remaining ETH to another account
   * @param targetAddress The address to transfer remaining ETH to.
   */
  function WithdrawAll(address targetAddress) onlyOwner returns(bool ok){
    if (targetAddress == 0x0) throw; //dont accidentally burn ether
    if(currentTerm > term && this.balance > 0){
      targetAddress.transfer(this.balance);
      bActive = false;
      return true;
    } else {
      throw;
    }

  }

  /**
   * @dev Allows the current owner to transfer control of the contract to a newOwner.
   * @param newOwner The address to transfer ownership to.
   */
  function transferOwnership(address newOwner) onlyOwner {
    if (newOwner != address(0)) {
      owner = newOwner;
    }
  }

  /**
   * @dev Allows the owner to send tokens elsewhere that were accidentally sent to this account
   * @param Token The address of the ERC20 token
   * @param _to The address the tokens should end up at
   * @param _value The amount of tokens
   */
  function safetyTransferToken(address Token, address _to, uint _value) onlyOwner returns (bool ok){
    bytes4 sig = bytes4(sha3("transfer(address,uint256)"));
    return Token.call(sig, _to, _value);
  }

}


The tests are in CoffeeScript and can easily be compiled to js. The core test concept here is the passage of time.
 
In the "it should allow withdraw after 1 month and ether goes to owner" test we simulate the passing of 34 days via our testrpc evm_increaseTime function mentioned earlier.

Our test code needs to do this multiple time over a set amount of time.  I use a variant of the following function:

withdrawFunction = ()->
  return q.Promise (resolve, reject)->
    web3.currentProvider.sendAsync
      jsonrpc: "2.0",
      method: "evm_increaseTime",
      params: [86400 * 34],  # 86400 seconds in a day
      id: new Date().getTime()
    , (err)->
      i.Withdraw(from: accounts[0])
      .then (result)->
        resolve result
      .catch (err)->
        reject err

We can call it in a loop like this:

async.eachSeries [1..13], (item, done)->
        #console.log item
        withdrawFunction()
        .then (result)->
          i.currentTerm.call(from: accounts[0])
        .then (result)->
          #console.log result
          done()
        .catch (err)->
          #console.log err
          done(err)
    .then (result)->
      assert(false, "shouldnt be here")
    .catch (error)->
      if error.toString().indexOf("invalid op") > -1
        #console.log("We were expecting a Solidity throw (aka an invalid op), we got one. Test succeeded.")
        assert.equal error.toString().indexOf("invalid op") > -1, true, 'didnt find invalid op throw'
      else
        assert(false, error.toString())
  done()

In the above case we call withdraw 13 times and expect it to fail on the 13th try.
 
We now have a great way to test the passage of time when testing our solidity contracts.  Since Catallax will likely do monthly this avenue of calculation may work.  We could manipulate our Decay Token to use timestamps instead of block numbers.  On the other hand, using block numbers allows us more control if we need to do some emergency decay as a monetary lever.

If this is interesting to you and you'd like to see where we are going with Catallax, please pick up my book Immortality(Purchasing a hard or kindle copy helps support this project.

Donations always accepted at:

BTC: 1AAfkhg1NEQwGmwW36dwDZjSAvNLtKECas

ETH and Tokens: 0x148311c647ec8a584d896c04f6492b5d9cb3a9b0