source code

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.