Creating the Smart Contract for the World’s First Interactive Coin Offering

Enrique Piqueras
May 28, 2018 · 10 min read

By Enrique Piqueras and Clément Lesaege

If you’ve been keeping up with all that’s been happening at Kleros lately, you know that we recently launched the world’s first Interactive Coin Offering.

Interactive Coin Offerings (IICOs) are a new type of Initial Coin Offering (ICO), created by Vitalik Buterin (Ethereum) and Jason Teutsch (Truebit), that implement a set of economic incentives and penalties that level the playing field between participants of all sizes. We wrote an article that explains it in more detail here, in case you are curious. This post will delve into the details as to how and why we developed the IICO contract the way we did.

The Challenge

The IICO protocol, as described in the whitepaper, presents the following user stories:

  • As a user, I can place a bid in any of the sale’s phases and optionally set a personal cap on the total amount raised, in order to participate in the sale at valuations I am comfortable with.

We also decided to add an extra phase at the start of the sale where the bonus does not decrease so that everyone can take advantage of the full bonus and to avoid harmful network congestions.

The Solution

Clément Lesaege, our CTO, went to work with these goals in mind and returned with a very elegant and succinct solution. Let’s now go over its inner workings.


The contract is deployed with the following parameters:

  • uint _startTime: The time the sale will start at.

The ways these parameters are used to initiate the contract are pretty straightforward. The times are added up to calculate when phases start and end, and the other values are just persisted into storage.

After that, something a bit more complex happens. The bids in our implementation are stored in a sorted linked list. We sort it by personal cap and bid ID in ascending order to allow us to easily find which bids remain in the sale and which bids should be refunded. To avoid writing “if guards” that check if the list is empty in all the other functions that interact with it, we add two bids, the head (with contribution 0) and the tail (with the biggest possible integer contribution), into the list. These are “virtual” bids with no contributor, that are just there to simplify the rest of the code.

Displaying Data

All of the contract’s storage variables are public, which means things like the phase times can be queried freely. However, we did add two extra functions with the purpose of displaying data in user interfaces.

  • bonus: Calculates the bonus at the current time, taking the current phase into account. This is also used internally to calculate the bonus for new bids.

These, together with all the public storage variables, expose all the data that is needed to build a comprehensive user interface, and that’s exactly what we did with OpenIICO, a platform for the future of token sales, that we will delve into in a future post.

Submitting Bids

This is where the meat of the logic lies. It all revolves around the fact that the list is sorted and searching through it to find the correct spot to insert at, is an O(n) computation and could potentially cost a lot of gas after a significant amount of bids are made. We get around this by offloading most of the computation to a contract view, which costs no gas. There are 3 ways to submit a bid and 4 functions involved:

  • submitBid: This function inserts a bid into the spot specified. It will revert if the spot specified is not the correct one, so it is not meant to be used alone.

This flexible design allows for users to place sophisticated bids with very little gas costs, and still allows for contributors to send ETH directly to the contract and not have to deal with a separate user interface than their wallet’s.


Withdrawing is pretty straightforward and it’s handled by just one function, withdraw. Calling it in the correct phases will immediately transfer your refund to you and lock in the remaining part of your contribution to the sale, with a reduced bonus.


This is the part where the final cut-off bid is found and the contract finalizes who has to be refunded and who has to be given tokens, or both when the cut-off bid is taken partially. It loops backwards through the entire list of bids, starting with the second last one, because the tail is not a real bid.

It keeps adding up all the contributions until it finds a bid that has a personal cap under the current running total, or that has a contribution amount that once added to the current running total, puts it over its personal cap. If the former, it refunds the entire bid, if the latter, it refunds the bid partially and accepts an amount such that the bid’s personal cap equals the running total. This running total then becomes the final valuation and is sent to the beneficiary.

The interesting part of this function is that it does not have to be called for the entire list at once. It caches the counters so it can be called by anyone, each specifying how many iterations of the loop they want to fund. This removes the dependence on a single party to finalize the contract.


Once the contract is finalized, anyone can redeem tokens and/or refunded ETH. Anyone can redeem a specific bid by calling redeem with the bid ID. It doesn’t have to be the bid’s contributor. We also built in the option of redeeming using the Fallback (ETH Receiving) function.

If you send 0 ETH directly to the contract, the contract will call redeem on all of your bids. This, combined with the last option in the “Submitting a Bid” section, allows contributors to participate in the sale and redeem, entirely from their wallet software of choice.


For our ICO, we extended the contract we talked about here and implemented KYC functionality to comply with regulations. Similarly, you can extend the contract to support any custom functionality you need. That contract can be found here. We also built a multi-purpose open source user interface for the contract, that can be found here.


We worked with CoinMercenary and VeriChains to audit the code.


VeriChains auditors reported two issues that they considered vulnerabilities.

Incorrect ordering of bids for the same personal cap.

“Bids are ordered in ascending order by personal cap and then ID. This is enforced in the submitBid method by the ordering requirement.

require(_maxValuation >= prevBid.maxValuation && _maxValuation < nextBid.maxValuation); // The new bid maxValuation is higher than the previous one and strictly lower than the next one.

During finalization, the cut-off bid is found by traversing the list from the last bid to the first bid. This scheme incentivizes users to “spam” by re-submitting the same personal cap, because their new bid will be included first during finalization.”

This is not a vulnerability as the personal cap is the amount where a bidder is indifferent to whether its bid is included in the sale or not. In other words, if the valuation equals your personal cap, it should not matter to you whether your bid is accepted or not. Moreover, even if bidders wanted to have their bid accepted at their personal cap, for which they should have just submitted a higher personal cap from the start, they would just have to re-submit their bid, adding 1 Wei to their personal cap.

The change proposed by VeriChains was to sort by bid ID in descending order instead of ascending order. However, this would have significantly increased the amount of code in the contract, which would, in our opinion, increase the risk of vulnerabilities.

Since this comment about ordering was made 4 times by different parties, we updated the README to make it explicit that in the case of tied personal caps, the last one will be taken first.

Possible integer overflow in bonus method

“A bid’s bonus is calculated using constructor parameters and this might lead to unexpected behaviors when certain parameters are used.

/** @dev Return the current bonus. The bonus only changes in 1/BONUS_DIVISOR increments.
* @return b The bonus expressed in 1/BONUS_DIVISOR. Will be normalized by BONUS_DIVISOR. For example for a 20% bonus, _maxBonus must be 0.2 * BONUS_DIVISOR.
function bonus() public view returns(uint b) {
if (now < endFullBonusTime) // Full bonus.
return maxBonus;
else if (now > endTime) // Assume no bonus after end.
return 0;
else // Compute the bonus decreasing linearly from endFullBonusTime to endTime.
return (maxBonus * (endTime — now)) / (endTime — endFullBonusTime);

In the above code, the multiplication maxBonus * (endTime — now) could overflow and return not-intended values.”

This cannot happen except for obviously malicious maxBonus values, and we explicitly stated that “The contract assumes that the owner sets appropriate parameters.”. A token sale providing something like a 1000000000000000000000000000000000000000000000000000000000% bonus, would obviously not be considered appropriate. In this case, the bonus would just become 0% would an overflow happen.


CoinMercenary auditors did not find any vulnerabilities.

“The reviewed smart contracts are well crafted and follow common security practices. No critical problems have been found. The Open IICO contracts are stellar examples of true craftsmanship. Working with contracts of this caliber is a rare experience.”

Bug Bounties

We set up bug bounty programs of 10 ETH and then 50 ETH maximum payouts and put them on Solidified. Two issues were found thanks to the bug bounties:

  • The manual withdrawal penalty was ⅔ of the bonus instead of ⅓ as described in the paper. The code was matching our documentation, but our documentation was not matching the paper.

Both of these were corrected before the start of the sale.

Do let us know if you have any questions about the implementation or if you come up with any improvements or suggestions. As always, you can reach us at one of the channels below.

Learn More

Join the community chat on Telegram.

Visit our website.

Follow us on Twitter.

Join our Slack for developer conversations.

Contribute on Github.


The Justice Protocol.

Welcome to a place where words matter. On Medium, smart voices and original ideas take center stage - with no ads in sight. Watch
Follow all the topics you care about, and we’ll deliver the best stories for you to your homepage and inbox. Explore
Get unlimited access to the best stories on Medium — and support writers while you’re at it. Just $5/month. Upgrade

Get the Medium app

A button that says 'Download on the App Store', and if clicked it will lead you to the iOS App store
A button that says 'Get it on, Google Play', and if clicked it will lead you to the Google Play store