The Smart Contract Risk in DeFi

Jan Xie
Nervos Network
Published in
8 min readDec 10, 2019
Photo by Jason Pofahl on Unsplash

Finance is the art of risk management. Risks exist in both assets and operations. An asset has a price, price is a reflection of its intrinsic value and synthesized risk. We can’t evaluate an asset without looking into its risk. The risks in operations are mainly from humans who are error-prone and corruptible. The evaluation of asset and operation risk is at the core of finance, no matter whether referring to traditional finance based on traditional assets or shiny new decentralized finance (aka. DeFi) running on native crypto assets.

Damage Control is the Key

The risk of crypto-assets is comprised of external risks such as regulation changes, and internal risks such as design flaws and implementation bugs. On Ethereum, the native asset is ETH, and the non-native assets are what we call ERC tokens, which refers to tokens that comply with any one of ERC20 standards and its companions such as ERC721 and ERC777 etc. The risk of the native asset is lower than the risk of a non-native asset because the latter can be affected by both Ethereum client bugs and smart contract bugs. For ERC tokens and DeFi, bugs in a smart contract are of the highest concern because DeFi as a system is a complicated network weaved together through endless smart contracts created by different developers from different places. We call the risks caused by smart contract bugs smart contract risks.

A lot of research efforts have focused on the vulnerabilities of smart contracts, and we have found many methods of defense. If you’re interested, here is a good survey. However, it’s commonly known that eliminating bugs in any non-trivial smart contract (or just any program) is impossible. We live in a world filled with communication errors and randomness, they cause “distortion” in each step: we can’t turn the idea in our mind into a precise specification, and similarly, we can’t convert a specification into a flawless implementation.

If this is the cruel reality, we should not only consider proactive and reactive defenses in our crypto-asset implementation but also damage control methods that can minimize the loss when something bad does happen, to avoid turning a butterfly into a black swan. There are many design patterns that can be adopted by smart contracts for damage control, the most important one (I believe) is the decentralization of application state because centralization of application state will amplify the damage caused by smart contract bugs. Let me explain in more detail what I mean exactly by that.

The Risk of ERC Tokens

ERC token standards differ in the interface but share some common features. The basic pattern of an ERC token is to use a token contract to manage the token ledger, and users interact with the token contract to issue, transfer or burn tokens. All records of the token ledger are stored in the token contract, and the contract itself is simply an account on Ethereum.

For example, suppose there’s an ERC token ‘Cup’, Alice has 100 Cups and Bob has 50 Cups. The token ledger is an Ethereum smart contract located at address/account 0x1234. This token contract 0x1234 maintains an internal database, which stores record like “Alice has 100 Cups” and “Bob has 50 Cups”. When Alice wants to transfer 30 Cups to Bob, she sends a signed message to the contract 0x1234 that says “please transfer 30 Cups to Bob”, the contract 0x1234 will then verify the message is indeed from Alice and modify its internal database, update the relevant records to “Alice has 70 Cups” and “Bob has 80 Cups”.

The problem here is that all logic and state are kept in the single contract 0x1234. Alice and Bob have no direct access to their own records because the records are kept in custody of the contract 0x1234. All users of this token interact with this contract, and the only way to manage their tokens is by sending a message to the contract 0x1234.

In other words, the token contract is a central point. The central point in any system is also the most lucrative attack point. Any problem with the central point affects all users because all tokens/records are kept by it and everyone has to interact with it. For example, with the ‘transferFlaw’ / ‘allowAnyone’ vulnerabilities caused by integer overflow or ‘ItchySwap’ vulnerability caused by cautious-less authorization, an attacker can steal others’ tokens while the owner has no interaction with the contract. With the ‘ownerAnyone’ vulnerability, an attacker can take control of the token contract to lock everyone’s tokens. In all of these examples, once the token contract is broken, everyone is in trouble.

An ERC token is held by a token contract, not by individual users, and this is very different from native tokens like Ethereum’s Ether. The records of ETH balances are not stored by any smart contract but are controlled by users directly. Every ETH owner has his/her own account and balance record. For example, if Alice has 100 ETH and Bob has 50 ETH on the Ethereum blockchain, Alice has a record “balance=100” in her own account and Bob has a record “balance=50” in his own account. Alice’s balance can only be decremented with the present of her signature, and the same is true for Bob’s account. The records of ETH ownership are decentralized in multiple accounts instead of centralized in a single account. As long as their private keys are safe, no one can steal ether from them. In contrast, the attacking interface of an ERC token is larger because an attacker can always try to break the token contract, which is much easier than breaking the protocol itself.

The different inherent risks make ERC tokens and ether two different classes of assets. A smart contract running on a decentralized network is not the same as the decentralized network. The centralization problem is introduced by ERC tokens again on the application layer, amplifying the potential damage of any smart contract bug, and unfortunately, we know humans will always make mistakes. There will always be bugs. The decentralization of the network/consensus layer can’t solve a centralization problem on the application layer.

How to Remove the Risk Amplifier

The point of centralization emerges in ERC tokens because their state is not a first-class citizen in Ethereum’s programming model. A piece of state is an attachment to code, but it cannot be referred to and compared directly. It’s natural to put state with the same validation rules (e.g. records of the same token) in the same contract, however, this contract then becomes a point of centralization. This is a consequence of Ethereum’s programming model.

On CKB, the pattern is inverted because the state is a first-class citizen. The state is the object users play with directly, and code is an attachment to the state. We can compare and group state with the same validation rules naturally, even when these states are held directly by different users.

We refer to a non-native token on CKB as ‘User Defined Token’ or UDT. For a given UDT, the asset definition (code) and asset records (state) are separated, and the records of different users (addresses) are separated too. The asset definition describes the logic of the token, e.g. “The issuance cap is 1M” or “Bob can issue new tokens”, and records are information like “Alice has 100 tokens”. The asset definition is a contract created by a token developer and stored in a cell owned by the developer (Asset Definition Cell), while the records are kept in users’ cells, and all use the same `type` script. Each user uses his/her own cell to store his/her own token records. These token records share the same validation rules defined by the asset definition cell. With this structure, the records are stored in a decentralized way.

As the above figure shows, Alice’s token is stored in Alice’s own cell and protected by her own lock script, which is Secp256k1 by default. Even if there’s a bug in the asset definition, an attacker cannot modify Alice’s record because doing so requires Alice’s private key. Because the token is held by Alice directly, there’s no way an attacker can work around Alice’s lock. By decentralizing the state of a token, the damage of a bug in asset definition can be controlled.

The possibility of bugs that affect everyone still exist: for example, there might be a bug in the asset definition that allows anyone to issue more tokens than expected. The benefit of UDT is, first, a large portion of bugs are eliminated; and second, the asset definition cell is protected by a lock which can be part of token issuance authentication logic naturally. It’s always easy to ride on the authentication mechanism provided by the protocol. This decoupling of authorization and business logic is a good engineering practice, and it is a default in CKB. UDT is more decentralized than ERC tokens, so the smart contract risk of UDT is lower than ERC token but still higher than a native token.

If you’re interested in UDT, there’re several discussions on Nervos Talk.

A Secure Nervos DAO

The decentralization of the application state can also help the DeFi application design. The Nervos DAO is the first DeFi application on CKB. It’s a smart contract with which users can interact the same way as any smart contract on CKB. One function of Nervos DAO is to provide a dilution counter-measure for CKByte holders. By depositing in Nervos DAO, holders get proportional secondary rewards, which guarantee their holdings are only affected by hard-capped primary issuance as in Bitcoin. There are over 1 billion CKBytes deposited in the DAO at the time of writing and the number is increasing, which makes it a very attractive contract for attackers. Should we be worried?

Maybe not so much, because the CKBytes locked in Nervos DAO are not pooled together in one single smart contract, but still held by different users! When a user wants to deposit into Nervos DAO, he/she constructs the deposit transaction by picking cells (UTXO of CKBytes) and set their type script references to 0x82d76d1b75fe2fd9a27dfbaa65a039221a380d76c926f378d3f81cf3e7e13f2e, which points to the Nervos DAO script. The lock scripts are left unchanged for those cells. Because the Nervos DAO type scripts are decoupled from users’ lock scripts, an attacker can never spend those deposited CKBytes without users’ permission.

To withdraw the deposited CKBytes, witnesses (signatures) to corresponding lock scripts must be provided. This validation is guaranteed by the CKB network, not by any smart contract, and there’s no workaround. The Nervos DAO script is there to make sure the compensation calculation is correct on withdrawal, not to hold funds.

The Impact on DeFi

It’s difficult to analyze the impact of smart contract risk on DeFi quantitatively, but we can get some clues from the insurance product provided by Nexus Mutual, which is a ‘decentralized alternative to insurance’. Its first product SmartContractCover allows a user to purchase insurance coverage on any smart contract so he/she can receive compensation if the smart contract is hacked. The premium of a SmartContractCover is determined by the market, positively correlated to the period and amount of the coverage. According to this post, insurance coverage of 1000 DAI in Nuo for 90 days will cost you 6.41 DAI, while insurance coverage of 1 ETH in Uniswap for 365 days will cost you 0.013 ETH. Based on these numbers, we can reach an estimation that the annualized smart contract risk cost is around 2%.

The 2% cost is a market inefficiency caused by smart contract risk, and that’s why an improvement in the smart contract programming model is important. A different programming model can reduce smart contract risk and bring us a more efficient DeFi market.

Thanks to Haseeb Qureshi, Cipher Wang, Matt Quinn and Christopher Heymann for their feedback on the draft of this post.

--

--