Smart Contract Safety: Best Practices & Design Patterns

smartcontract

#1

via sitepoint

Blockchain technology is still a highly experimental technology. Things move fast and you should expect constant changes in the security landscape, as new bugs and security risks are discovered and new best practices are developed. Following the security practices described in this article will help you better understand how to design flawless Ethereum smart contracts.

Developing with a security mindset is extremely important as the cost of failure can be high and change can be difficult. A basic defence mechanism against known vulnerabilities is therefore not enough. It’s recommended to use secure design patterns like rate limiters, exit strategies or circuit breakers to protect your contract against unexpected events.

Prepare For Failure

Any non-trivial contract will have errors in it. Your code must, therefore, be able to respond to bugs and vulnerabilities gracefully. When writing code, bear in mind the following security design patterns.

Checks-Effects-Interaction Pattern

Report Advertisement

This is the general guideline when coding any smart contract as it describes how to build up a function. You start with validating all the arguments and throwing appropriate errors when the args do not apply with the expected input. Next, you can make changes to the smart contract’s state and finally interact with other smart contracts.

Interacting with other contracts should always be the last step in your function as this mostly includes handing over the control to another contract. So, when we are handing over the control, it’s crucial that the current contract has finished its functionality and does not depend on the execution of the other contract. Here’s an example that comes from an Auction contract to set the end of the auction using a boolean.

function auctionEnd() public {
// 1. Checks
require(now >= auctionEnd);
require(!ended);
// 2. Effects
ended = true;
// 3. Interaction
beneficiary.transfer(highestBid);
}

view rawchecks_effects_interaction.sol hosted with ❤ by GitHub

Circuit Breaker

A circuit breaker, also referred to as an emergency stop, is capable of stopping the execution of functions inside the smart contract. A circuit breaker can be triggered manually by trusted parties included in the contract like the contract admin or by using programmatic rules that automatically trigger the circuit breaker when the defined conditions are met. The most common usage of a circuit breaker is when a bug is discovered.

Join Our Front-End Newsletter

Sent out every Friday, full of all the curated links to help you explore this emerging, complex world.

SIGN ME UP

bool private stopped = false;
address private owner;
modifier isAdmin() {
if(msg.sender != owner) {
throw;
}
_
}
function toggleContractActive() isAdmin public
{
// You can add an additional modifier that restricts stopping a contract to be based on another action, such as a vote of users
stopped = !stopped;
}
modifier stopInEmergency { if (!stopped) _ }
modifier onlyInEmergency { if (stopped) _ }
function deposit() stopInEmergency public
{
// some code
}
function withdraw() onlyInEmergency public
{
// some code
}

view rawcircuit-breaker-solidity.sol hosted with ❤ by GitHub

The above code example defines two modifiers. The first modifier makes sure that all actions in the contract are suspended when a bug is discovered – only the admin is able to toggle this boolean. However, we want to be able to withdraw funds in case of a severe bug; therefore, the second modifier is meant to allow you to withdraw funds.

Rate Limit

The problem we are trying to solve is a request rush on a certain function that can hinder the correct operational performance of a smart contract. The solution is quite simple: we regulate how often a task can be executed within a period of time. We can also limit the amount of Ether an owner can withdraw from a contract to prevent a rapid drainage of funds. Or another example is limiting the number of issued tokens over time at the contract level – every month the contract will distribute 10,000 tokens.

contract RateLimit {
uint enabledAt = now;
modifier enabledEvery(uint t) {
require(now >= enabledAt, “Access is denied. Rate limit exceeded.”);
enabledAt = now + t;
_;
}
function f() public enabledEvery(1 minutes) {
// some code
}
}

view rawrateLimitSimple.sol hosted with ❤ by GitHub

This piece of code shows a very basic rate limit modifier that accepts an amount of time to stop the contract from executing the function until the actual time is greater than the rate limit timer.

Speed Bumps – Delay Contract Actions

Speed bumps are extremely useful when malicious events occur as it gives a smart contract owner time to act accordingly. For example, The DAO had a 27 days delay installed in which no functions could be executed, leaving the funds in the contract, increasing the likelihood of recovery. However, the speed bump was pretty useless for The DAO case as no recovery actions were present. Therefore, a speed bump pattern should be used in combination with a circuit breaker that blocks the contract except the withdraw function. This ensures users are able to withdraw funds before the malicious action is fulfilled.

struct RequestedWithdrawal {
uint amount;
uint time;
}
mapping (address => uint) private balances;
mapping (address => RequestedWithdrawal) private requestedWithdrawals;
uint constant withdrawalWaitPeriod = 28 days; // 4 weeks
function requestWithdrawal() public {
if (balances[msg.sender] > 0) {
uint amountToWithdraw = balances[msg.sender];
balances[msg.sender] = 0; // for simplicity, we withdraw everything;
// presumably, the deposit function prevents new deposits when withdrawals are in progress
requestedWithdrawals[msg.sender] = RequestedWithdrawal({
amount: amountToWithdraw,
time: now
});
}
}
function withdraw() public {
if(requestedWithdrawals[msg.sender].amount > 0 && now > requestedWithdrawals[msg.sender].time + withdrawalWaitPeriod) {
uint amountToWithdraw = requestedWithdrawals[msg.sender].amount;
requestedWithdrawals[msg.sender].amount = 0;
if(!msg.sender.send(amountToWithdraw)) {
throw;
}
}
}

view rawspeed_bump_advanced.sol hosted with ❤ by GitHub

This speed bump Solidity contract delays a withdrawal request for 4 weeks.

Balance Limit Pattern

It is generally a good idea to manage the amount of money at risk when coding smart contracts. A balance limit pattern is a fairly easy pattern to implement, yet so strong. The pattern will monitor the amount of Ether in your contract and reject payments when it the balance exceeds the predefined maximum.

contract LimitBalance {
uint256 public limit;
function LimitBalance(uint256 value) public {
limit = value;
}
modifier limitedPayable() {
require(this.balance <= limit);
_;
}
function deposit() public payable limitedPayable {
// some code
}
}

view rawbalance_limit.sol hosted with ❤ by GitHub

This piece of code uses a modifier to limit the balance inside the contract. The modifier is applied to the ‘deposit’ function as that’s where the money is being accepted.

iOlite Plugin for Designing Secure Contracts

The iOlite project is a crowd intelligence platform for allowing non-programmers to write secure smart contracts with natural language that they can use to build blockchain applications.

In addition, the iOlite team has developed a plugin for developers that turns any programming language into Solidity code (first among different blockchain languages) using structs attached to snippets of code defined by Solidity experts.

The plugin for Visual Studio Code can be found on the marketplace. The full documentation can be viewed on Github.

The Bottom Line

Ethereum allows Solidity smart contracts to run autonomously on the blockchain. However, this requires highly secure code which implements all of the above design patterns.

Actually, the main problem that these patterns solve is the lack of execution control once a contract has been deployed. It’s both a drawback and an advantage for smart contracts. If you are a developer, be careful when deploying a smart contract and test each smart contract thoroughly — a single mistake can cost you millions.