The Making of an Open-Source Token Vesting Smart Contract
In this article, I’m going to talk a bit about the development of the CPUcoin token vesting smart contract, a significant piece of work that we painstakingly developed for our upcoming IEO (initial exchange offering). At the end of this article, I’m going to give away our code. Yes, you are free to use, modify and share our work (in keeping with our MIT license).
In future articles, I may delve into the code to explain in more detail how it works. But for now, I’m going to discuss what went into its design. So, if you happen to be involved with a project having requirements for a token needing support for grants with flexible vesting schedules, then this information may be of interest to you. Our solution is tailored somewhat to our own needs, however, we made it general purpose enough so that you can easily reuse parts of it for adapting to your needs.
Smart Contract Pitfalls
Our aim is to successfully launch our IEO (initial exchange token offering) to make our ecosystem cryptocurrency tokens available for sale in the marketplace. As you may know, to create a working cryptocurrency token, you must develop a smart contract, the software code that defines all the rules and behavior of your new currency. We are releasing our token on the Ethereum blockchain, and in that environment, the smart contract programming language is known as Solidity.
You may also know that once a smart contract is released it is final. Your code cannot be retracted, changed, updated or patched to correct problems if a bug is discovered later or you find you have missed something. Once the code is released, it is stored on the blockchain. And the blockchain is, by design, built so that the past is immutable. No means has been provided to mutate smart contract code. A software bug in a critical portion of your smart contract could easily destroy the value of your token before it gets off the ground. Here are just a few of the scenarios which have actually occurred in real-world live smart contracts, dooming many of them:
Unsigned math underflow
- A software bug causing an unsigned math underflow to occur due to bad inputs. This results in a calculation unexpectedly producing a negative number, which when represented as an unsigned value will always appear incorrectly as a huge positive amount. Once discovered, hackers can exploit this by carefully crafting bad inputs to your contract, letting them create an immense number of tokens for themselves. If the inputs had been checked up front, the hacker would have instead seen a mundane error.
Incorrect method visibility
- A software bug in an internal, private method performing some critical administrative function that was written with the method having a wrong visibility set. This causes that intended-as-private code to unexpectedly become exposed publicly to everybody. Nothing hides on the blockchain after all, and hackers who study your code will discover the “hidden” administrative function and try calling it themselves. Depending on the power of the internal code, the hacker may get the equivalent of root access to your contract, taking over control of it from you. If visibility had been specified as `internal` or `private`, the method code would not be reachable externally.
Unknowingly calling malicious code
- A software bug occurs that because a routine call is made, such as to transfer funds, without the calling code realizing that the target address of the transfer is actually the address of another smart contract, not a wallet. A hacker who studies your code could craft and deploy a special smart contract whose purpose is to attack yours. The hacker implements an identical `transfer()` method that contains malicious code carefully designed to cause your smart contract into a state it cannot get out of, thereby locking it up. Or worse, it could force your contract to self-destruct, thereby sending all your ETH to the attacker! There are ways to write contract code to be immune from this type of threat.
There are a number of well-known ways in which smart contracts can and have failed. There are also well-documented best practices one must follow when writing smart contract code to avoid these pitfalls. It’s perhaps a fledgling startup’s biggest fear that their IEO or ICO gets sabotaged by hackers, with real monetary value slipping away never to be recovered. At best, the sale would have to be halted, a new smart contract is written and the IEO/ICO process restarted. But this would likely still be a damaging outcome, whose full resolution would be very onerous.
Know Your Requirements
Besides writing code resistant to attack, you must be sufficiently complete in defining all the features, functionality and behavior that your organization will need to create to serve its operational needs. Your project is a business, after all, and if some internal requirement was overlooked, you could end up with a token that doesn’t support a critical workflow. This could drastically constrain your internal operations, and limit management and administrative control of your token later.
It can be difficult to surface all future business needs up front. Often the people who understand the business needs, the people who understand the operational needs and the people who will write the code are different individuals in your organization that come from different, unique perspectives. At best they have only partially overlapping knowledge and experience. It’s difficult for any one stakeholder to know the needs of the other stakeholders, and indeed, each may not know up front what their own needs are.
Therefore, it’s critical to begin with a comprehensive planning and design process up front, one which includes all stakeholders and takes the time to surface all needs. Expectations around your company’s token contract development process should be clearly defined, set and agreed to up front. The result of your hard work must be not only bug-free but, but must also be substantially complete, having support for all the features you will need now and later.
Our Vesting Token Requirements
In our design and planning requirements, we came up with the following basic needs for our IEO vesting token:
- A token contract based on prevailing standards that would be generally compatible with exchanges
- An ability for us to offer vesting purchase incentives to buyers of our token, giving us enough flexibility to create a variety of deal scenarios that allow us to entice different types of buyer
- An ability to incentivize our dedicated and hardworking employees with vesting token grants
We quickly realized that we needed to build in general support for vesting schedules. Vesting schedules are not difficult to understand, define and create. The code for this should be straightforward, right? But we kept digging deeper and asked ourselves more questions. The more we thought about the big picture, the more we realized that we’d need additional capabilities. Soon we had a lengthy list of features and needs.
We discussed and debated the merits of each requirement, forging the list down to a manageable set of only the truly necessary requirements. This work forced us to think deeply about future operational processes that hadn’t yet been discussed, let alone established.
- Our white paper dictates that we have four pools: 72% for the IEO, 10% for the team, 10% for advisors/partners and 8% for token swaps. How do we enforce these? We’re going to need to be able to operate some form of individually administered token pools which conform to the rules we’ve laid out.
- Employee grants at first appear to be quite like bonus tokens granted to buyers, just having different vesting terms. Yet there is at least one key difference: employee grants must be revocable if the employee leaves or is terminated, whereas buyer grants are always purchased, are non-refundable and therefore must not only be irrevocable (and there must be no way to accidentally revoke them). We’ll need simultaneous support for these use cases.
- Considering administration, who will control the smart contract? Is the person who issues employee grants the same person who issues buyer bonus token grants? We needed a way to separate duties and responsibilities for internal business process reasons. More complexity!
- And, what if… heaven forbid, something were to go horribly wrong during the IEO. Maybe someone makes a huge irreversible mistake, transferring a large sum of funds to an invalid Ethereum address? Maybe a hacker finds an exploit in our contract and kills or seizes control of it mid-IEO? We needed at minimum to have a way to halt the smart contract so that we could pick up the pieces and start again with a better solution.
We took some time to consider our findings so we would be sure to support all the uncovered necessary token issuing scenarios and administrative functions.
Next, I will tell you a bit about specifically what we came up. If you are involved with a project that is doing an IEO or an ICO, this may be of interest to you.
1. Start with a strong foundation
For a compliant token ecosystem, we wanted to create a next-generation ERC-20 smart contract that correctly provides a typical structure for managing token grants to employees, founders, advisors and third parties, such as exchanges or broker-dealers.
After searching high and low through available open source solutions such as thisand that, we found available solutions to be a bit too simplistic for our needs. There may be more applicable implementations available already but we weren’t able to find them. So we decided to create our own.
Not wanting to completely reinvent the wheel, we chose to base our contract on the solid foundations of the tried and true: proven battle-tested code that implements the underlying needs of our token, namely the ability for it to adhere to a standard (ERC20) and capabilities for creating and administering roles. That led us to the good folks at OpenZeppelin.org who offer exactly what we were looking for, in the form of reusable, tested open-source code that follows best practices, available now at OpenZeppelin-Solidity.
2. Analyze our own needs in depth.
We needed to deliver on specific functionality, such as:
- Ensuring that we can enforce compliance requirements
- Incentivizing different parties in a similar manner to options vesting
- Enabling a way for tokens that are not vested to be returned to the pool through revocation
After discussions with several token exchanges it became clear that, for doing IEO’s and token launchpads, it would necessary to provide exchanges both unlocked pools and templated pools. This would allow for use cases like delivering tokens with uniform 12-month unlocking schedules where there can be no mistakes during token issuance.
Additionally, after deciding on a grant mechanism based on uniform grant pools, we thought about workflows interacting with exchanges and realized we’d need to be periodically able to top-up the exchange wallet from which tokens are issued, making sure that we enforce appropriate restrictions around how the exchange may issue token grants to buyers.
The outcome was a very flexible design that lets a wallet holder self-register to confirm coins won’t be delivered into a wrong wallet by accident, for large pool delivery or otherwise.
One of the key requirements that we believe was missing in existing solutions is an enterprise-style roles and permissions mechanism allowing us to create a proper structure for managing and growing an organization. When your organization is only 10 people, this might seem like overkill. When you grow to hundreds of employees or even larger, as startups hope to achieve, having proper organizational controls becomes very important.
Additionally, we’ve provided the ability to create tools to administer, approve and centralize or decentralize token granting within different departments, while always having “Super Administration” capability.
3. Build upon the foundation, adding capabilities in a modular fashion.
This was done by writing contract code for each part of the overall behavior in isolation and then combining the contracts together to create the whole working system. This best practice is widely encouraged for the development of nontrivial smart contracts. A solid design principle is to build software in units that do one thing.
Here is a summary of what we’ve built:
Flexible Revocability Controls
- Simultaneously supports both revocable and irrevocable token grants. Use cases for this are token purchases (irrevocable) as well employee compensation plans or advisors (revocable).
- At the time of grant, tokens get deposited into the beneficiary’s account, but only the vested (released) portion can be spent. We like this model because it ensures the not-vested tokens are stored safely with the beneficiary and remain locked, with no possibility of unreleased tokens being accidentally spent out of a storage wallet or appropriated for some other purpose.
- If a revocable token grant later is revoked by the grantor, the beneficiary gets to keep the vested tokens, and the remaining unreleased tokens will be immediately returned to the grantor’s pool whence they came.
Grantor Roles and Pool Templates
- We built support for a grantor role, a Boolean state which can be assigned to one or multiple Ethereum accounts. This gives us the ability to form any number of funded grant pools of to be used for different classes of token grant, each having its own representative individual serving as grantor.
- Each grant pool can be assigned its own uniform vesting schedule to be applied equally to grants made to all beneficiaries from that pool. Restrictions can be set to parameterize limits on the grantor’s ability to set start dates. A grantor expiration date can also be set to automatically close the pool on a certain date. These restrictions can be changed administratively by us later if it becomes necessary.
- There’s also an ability to create one-off grants, where each beneficiary has a unique, unshared vesting schedule associated to its grant.
Traditional vesting, cliff, start date, intervals
- The vesting schedule mechanism supports four parameters: start date, cliff date, end date, and interval, all measured in days. If the interval is set to 1, the grant will vest linearly. If the interval is a set to number such as 30, vesting bumps up every 30 days. There is a restriction that both the length of the cliff and the overall duration must be an even multiple of the interval. This flexibility allows for many possible vesting patterns.
- All grant-related dates are counted in whole days (by convention, the number of days since the Unix epoch, or January 1, 1970), with each day starting at midnight UTC time. This locks all vesting to the same clock to help facilitate orderly bookkeeping. It also disallows absurd cases like grants that are 15 minutes long, etc.
- We decided on having a limit of one vesting grant at a time per account. A beneficiary can still have multiple grants in effect on different terms simply by providing us a new Ethereum account to be used for each new grant.
Safe Delivery Registration
- We included support for an address self-registration mechanism, along with “safe” methods that only transact with a verified address. These are provided to help prevent token loss through accidental bad data.
Roles and permissions
- We provided enterprise-style support for roles and permissions, which also utilize the safe delivery mechanism to prevent loss of ownership control or accidental transfer of ownership to an invalid address.
Tests, out of the box
- Full automated test coverage of the contract code is provided, written in node.js.
- We decided not to support multiple simultaneous vesting schedules for one beneficiary. Implementing that looked to be complicated and transactions even with one vesting schedule, the primary use case, would have cost a lot of gas. A workaround is simply to use more Ethereum accounts if needed, one for each additional token grant.
- We don’t support the continuous release of tokens throughout a day (as explained above).
We at CPUcoin will soon launch our IEO and we hope you will find our vesting token implementation useful. Even if you’re not planning to use the code yourself, your review of it is most welcome and we are eager to hear any thoughts you’d like to share. Please be warned that it’s still brand new code. While it has been tested for most important use cases, it has not yet been used in production so there is the possibility it could still contain a bug or two.
We thank you for any feedback offered here or in GitHub!