Colony Sale Contract and the CLNY Token
Although the unfortunate reality of regulatory attitudes to token sales has led us to postpone the Colony Token sale until next year, we think it’s worthwhile to open-source our sale code to help others who might be looking for resources on token sale design. (You can read more about the decision in our recent announcement.)
This article provides an overview of the Colony Token Sale contracts implementation at https://github.com/JoinColony/colonySale
We wanted to create a sale which was simple to understand, did not require pre-registration, and would not result in selling out rapidly to just a few accounts. At the same time, we didn’t want the amount it was possible to raise to be totally unbounded.
We chose to go with a simple soft cap: if the soft cap is hit, then the sale closes after the greater of either three hours, or the time taken to hit the soft cap, up to a maximum of 24 hours.
The core principles followed in the design were: security, simplicity and upgradability. Additionally, we strove to minimise network impact and gas costs for buyers.
For more on the sale requirements we implemented, see the repo issues list.
The Ethereum development community is based on an ethos of collaboration. Credit goes out to the people and organisations responsible for the contracts we reused and the ideas that influenced this implementation.
EtherRouter design pattern — by Ownage
This pattern utilises solidity assembly for low-level routing of calls and was used to implement our static address and fully upgradable token. Implementation details in the “Upgradable Token” section below.
ERC20 Token, Math and Authorisation contracts — by DappHub
The Colony Token reuses the DS-Token implementation in combination with the aforementioned EtherRouter pattern. We also extensively reuse the safe arithmetic functions of DS-Math as well as the DS-Auth permissions implementation.
MultiSig Wallet — by Gnosis / Consensys
Developed for the Gnosis crowd sale, see overview here
Statement following the discovery of the Parity MultiSig vulnerability https://blog.gnosis.pm/the-gnosis-multisig-wallet-and-our-commitment-to-security-ce9aca0d17f6
Token sales done better — by Nick Johnson
This article kicked off a very productive internal discussion that led to the focus on lowering the overall network impact. See “Gas cost optimisation and the phantom finney” section below.
In line with our main principles, the main ColonyTokenSale.sol contract inherits nothing more than the DS-Math contract for safe arithmetics and has just 4 core functions in addition to the fallback and emergency-only start/stop sale functions.
buy()— called by the payable fallback function which firstly forwards the contribution to the Colony MultiSig wallet, then adds the
msg.valueto the current user’s contribution tally, and finally updates the
Note that the single transaction which crosses the
softCap value will execute additional logic to determine whether the sale
endBlock will change.
finalize()— will execute successfully only once after the sale
endBlockhas been reached to check whether the sale was successful, if so, it will mint and distribute the preallocated tokens to purchasers, team members and the network fund. It also allocates Token grants for the Colony Foundation and Team pool.
claimPurchase()—calculate and transfer the purchased tokens to the buyer. This function is going to be called by Colony for each buyer in order to issue the CLNY tokens after the sale is finalised.
claimVestedTokens()— claim any vested tokens from the token grants created when sale is finalised.
start() functions are switching a boolean flag to indicate whether the sale is stopped in case of an emergency. This safety switch is a common pattern in ICOs guarding against attacks.
Gas cost optimization and the phantom finney
In earlier versions of the sale contract, gas prices for buy transactions were at around ~120,000 gas which was far too high.
The first way to reduce cost for CLNY purchasers was by refactoring out token issuance from the
buy() function. Now, the
claimPurchase() function is run once for every buyer after the sale concludes, and that gas is paid by Colony, saving ~185,252 gas for each buyer.
After that reduction, we noticed another discrepancy:
Changing a value from zero to non-zero
SSTORE operation incurs an additional 20,000 gas. In the sale contract, the first time the
buy()function is called, the
totalRaised value needed to be changed from
0to some value — this meant that the first
buy() transaction ended up with a gas cost of around 35,000 more than all subsequent
It seemed like bad luck to pass this cost onto the most eager and luckiest of CLNY buyers, and also would have required us to advise buyers to set their gas limits higher than expected. So, a workaround was devised:
totalRaised value is set initially to
1 finney, eliminating the extra cost from the first buyer. The ‘phantom finney’ is then subtracted from
totalRaised when the
finalize() function is called.
See commit 3ab46c65153c5bae10f8d840b9398434e2f6a2ea for more information.
buy() transaction now consumes at most 56,944 gas.
Note that all but two
buy() transactions actually cost just 41,994 gas. The two that differ are the very first
buy() transaction (56,994 gas) and the one that executes the
endBlock update logic (53,618).
The 20K gas the phantom finney saves in the first transaction has a much wider impact than the 2 cents it saves the first buyer. It helps to lower the total impact of the token sale on the network. Although the block gas limit has been increasing, the transactions queue still tend to stay high especially during a popular ICO.
Assume that there are 10,000
buy transactions in queue, and that each of them sends 20K more gas than their actual cost. Because gas is used by miners to fill blocks, about 21 fewer
buy transactions would make it in to each block.
That would mean that for every 10,000 buy transactions, the network would use ~42 extra blocks to process.
If we were to include the token minting and transfer calls that will jump up to ~110 extra blocks (calculated with the current block gas limit of ~6,500K).
The third pillar we built the code on was upgradability. This is key for the Colony Token as it will undergo future developments as described in the Colony Whitepaper, yet the token address must stay static for clients and exchanges to reference.
Our implementation is based on the EtherRouter design pattern which uses assembly level
delegatecall to forward calls to the upgradable Token contract via a simple, immutable EtherRouter contract that retains its address over time. The Resolver contract holds the token address and function signatures and serves as a routing repository for the token functions.
In addition the MultiSigWallet.sol provides the control over the Token upgrade.
Contract Testing and Reviews
The ColonyTokenSale.sol and Token.sol contracts (including EtherRouter.sol and Resolver.sol) have been tested using truffle/testrpc setup, see https://github.com/JoinColony/colonySale#testing
We maintain 100% code coverage in tests, automated using solidity-coverage.
Additional, contracts have been linted using Solium which checks against Solidity style guide.
Code has been audited by DappHub:
Finally, for the love of ETH, and all that is decentralised, please don’t use this contract to sell pre-functional tokens. If you need to raise money, use this or this with people you approach directly instead.
Elena is a Solidity developer.
She was a corporate warrior in a previous life, but left her high heels and life in London to go backpacking for a year before falling in love with Ethereum and joining Colony.
She now lives in Bulgaria where, while not writing smarter contracts or helping in the Truffle Gitter channel, she goes off-roading in her Jeep.
If you have any feedback or stories to share, reach out to us on Twitter.
Colony is a platform for open organizations.