Chapter 12. Smart Contract Security

Being able to write a smart contract is only one aspect of blockchain development. In Ethereum development, writing secure code that has been thoroughly tested and audited is essential. If your contract is hacked, you can’t easily take it down and fix it. Smart contract security is a serious issue and it can be very costly if a mistake is made.

In this chapter, we’re going to talk about smart contract security and various techniques we can use to secure our contracts.

Why Do We Need to Worry About Security?

One reason why having a secure smart contract is so important is that you cannot edit your contract once it’s on the blockchain. Once it’s out there, it’s not possible to patch a fix like we can in other types of software development. Ether can become stuck in the blockchain and impossible to release.

As an example, Parity’s Multi-Sig contract was hacked on November 6, 2017. A hacker found a small bug in the contract and disabled it. The entirety of the $300 million is lost forever.

In 2016 there was a large hack of the Decentralized Autonomous Organization (DAO), which was created as a decentralized investment fund. A group of unknown hackers found security issues that could be exploited in the contract. The contract should have first removed the ability to call the contract again, but instead it allowed users to recursively call the contract, and the hacker was able to retrieve 3.6 million ether.

Let’s discuss a few ways your smart contract can be hacked.

Types of Smart Contract Vulnerabilities

There are many different ways that your smart contract could be vulnerable to an attack. We’ll discuss a few common attacks in this section.

Unprotected Function

As you write your smart contracts, you should always ask yourself what roles should be allowed to invoke the function. If only certain addresses or addresses with specific roles can perform a function, then make sure to use appropriate require statements or modifiers to prevent unauthorized access.

In addition to what addresses can perform certain functions, you should consider what context can invoke the function and then use the appropriate visibility modifier. For a refresher, refer to Chapter 4 where we discussed visibility modifiers during the creation of our Greeter contract.

As an example of how to protect your functions, let’s examine the Ownable contract from OpenZeppelin. The following snippet is for the transferOwnership function:

function transferOwnership(address newOwner) public onlyOwner {
    _transferOwnership(newOwner);
}

Notice the function visibility of public and the onlyOwner modifier. This means it can be called from anywhere, but this is restricted to the owner of the contract. The function then delegates to _transferOwnership, which is shown in our next snippet:

function _transferOwnership(address newOwner) internal {
    require(newOwner != address(0), "Ownable: new owner is the zero address");
    emit OwnershipTransferred(_owner, newOwner);
    _owner = newOwner;
}

The function visibility has been marked internal, which means it can be called from within the contract or one of its derivatives. This is a great practice for locking down who can call a function.

Now that we can protect our functions, let’s talk about transaction ordering.

Transaction Ordering Dependence

Transaction ordering is a race condition attack where the attacker raises the gas price of a transaction in an attempt to have their transaction executed first. The more you pay in gas, the higher priority your transaction receives from miners. This means someone observing the mempool of pending transactions could jump the line if they see a transaction come in that would be detrimental to their interests.

This attack is very difficult to defend against and commonly targets applications in which timing is important, such as auctions or marketplaces. To mitigate this attack, try batching transactions or separating the details into a separate request.

Integer Overflow and Underflow

Integer overflow and underflow is an issue where a result is produced that is larger than the maximum or smaller than the minimum value of an integer. For instance, if we have a variable with the uint8 data type, its maximum value is 255. If you were to add 1 to 255, an overflow would occur and the result would be 0. Now that we have 0, if we were to subtract 1 from it, we would underflow and return 255. Instead of allowing overflows and underflows, we recommend using SafeMath when performing arithmetic operations.

For an example of how to use SafeMath, refer back to Chapter 6, Example 6-15, where we were adding the donate functionality to our Fundraiser contract.

The next vulnerability we will look into is reentrancy.

Reentrancy

A reentrancy attack occurs when an important state variable is changed after a contract calls a function of another contract or invokes the fallback function of another contract. In these cases, the malicious contract is able to call back into the original contract, invoking the same function again before the state has been updated.

To avoid this attack update the state of your contract before calling a function on any contract you do not own or before sending or transferring to any address that may be added by your users. It is also best to avoid using lower-level functions on addresses like call when dealing with unknown or untrusted addresses.

Here’s an example of a reentrancy. The attacker finds the vulnerability in the contract where two functions rely on the same state. And the hacker is able to call the function again before the balance is set to 0. This was the attack used by hackers in the DAO attack in 2016. The attacker recursively called certain functions to hack the contract, thus exploiting a reentrancy vulnerability in the contract.

Let’s take a look at cross-function race conditions, which is another type of reentrancy.

Cross-function race conditions

One interesting attack is when multiple functions share the same state. An example of this is a contract where your balance is reset to 0 only after the transfer occurs, so the hacker could keep calling this even after they’ve received the withdrawal to hack more ether from the contract. Basically, they can call another function halfway through the first function’s execution to hack your contract.

Here’s an example:

function withdrawFunds() external {
    uint256 withdrawAmount = balances[msg.sender];
    require(msg.sender.call.value(withdrawAmount)());
    balances[msg.sender] = 0;
}

The balance is not set to 0 until after the other function is called. The attacker can call this recursively and successfully hack the smart contract. One way to protect against this attack is to update the balance before executing call.value on the sending address:

function withdrawFunds() external {
    uint256 withdrawAmount = balances[msg.sender];
    balances[msg.sender] = 0;
    require(msg.sender.call.value(withdrawAmount)());
}

It may seem simple, but this reordering of lines could save quite a few smart contracts from being compromised.

Up next, we’ll talk about running out of gas.

Block Gas Limit

The Ethereum network, specifically the EVM, can only process a certain computational load at a time. This restriction is conceptualized in the form of the block gas limit, as discussed in Chapter 1. If you surpass the limit, the transaction fails. This can happen when a function processes a significant number of state changes like when iterating over a large array.

To avoid exceeding the gas limit, break large—or potentially large—operations into batches. The pagination function we implemented in Chapter 7, Example 7-12, could be a great example for how to break things up into smaller chunks.

Timestamp Dependence

Because the blockchain is decentralized, it relies on the computational power of distributed nodes (miners) instead of centralized servers to process transactions. The timestamp of the miner can therefore be manipulated by their owners in an attempt to circumvent any logic based on timestamps within a contract.

In order to avoid this vulnerability, make sure your contract does not use timestamps for randomization seeds. It is also a good idea to make sure that your conditional logic can tolerate 15 second variance and still provide the desired behavior. If you are wondering why 15 seconds are required, it is because both Parity and Geth would reject a block that is more than 15 seconds into the future.

And Many More

These are just a few of the common attacks against smart contracts. It’s important to stay updated on the latest types of hacks and keep your contracts secure. ConsenSys provides a list of common smart contract attacks that you should review when writing your smart contracts.

If you intend to release your smart contract to the public, it’s important to have a smart contract audit.

Preparing Your Contract for an External Audit

Before you outsource to an external company for an audit of your smart contract, you should first try to secure it as much as possible and then try to hack it yourself. Let’s discuss a few ways we can try to find a vulnerability in a contract.

  1. Your contract should have 100% test code coverage. Smart contracts must always have tests. Test-driven development (TDD) is a great option for keeping your contracts safe.

  2. Run your tests. Try to break each test and see what info you can glean from that.

  3. Check for reordering, replay, or short address attacks.

  4. Compile your contract and check for warnings.

  5. Verify that your contract has no race conditions, reentrancy issues, or cross-function race conditions.

  6. Check for transaction ordering or timestamp dependence.

  7. Check for integer overflow and underflow.

  8. Check for an unexpected revert.

  9. Check for denial of service (DoS) with block gas limit.

  10. Check for method execution permissions.

  11. Verify that OpenZeppelin and your Solidity versions are up to date.

  12. If you use a library in your smart contract, verify that you’re using it correctly.

  13. Check that the contract uses SafeMath.

  14. Verify that you don’t have any compiler warnings.

CryptoFin provides a great checklist for securing your smart contracts here.

These are only a few examples of ways you can secure your smart contracts. The best thing to do to create a secure smart contract is to find an external auditor. It’s not always easy to see potential issues on code you’ve been working on.

External Auditing

One of the best ways to mitigate security vulnerabilities in your smart contract is to hire an external auditor or a smart contract auditing company. An external auditing company will look at your contract with fresh eyes and run it through different auditing programs to find any security issues. The smart contract auditor will provide you with a list of issues found in your contract and ways you can fix them. Securing smart contracts has become such a large issue that entire companies now exist to audit and provide ways to secure smart contracts.

Auditing Companies

Some of the larger smart contract auditing-focused companies provide public audits of past smart contracts. One example of that is Authio where you can see examples of vulnerabilities they found and recommendations of how to fix them. If you’re interested in smart contract security, many auditing companies make these audits public, and you can read through them to get better at spotting out potential security issues in your own smart contracts.

Solidified

Another way to check if your smart contract is secure is to post it on Solidified. Solidified is a platform where you can post your smart contract and a group of Solidity experts, bounty hunters, and developers will work together to scout out vulnerabilities. If you’re interested in becoming an auditor, you can become one at Solidified.io as well. Solidified is great because it first provides a team of Solidity experts to find vulnerabilities in your contract. After they’ve finished their report, they’ll provide suggestions on how to fix the issues in your smart contract. Once you’ve fixed the issues in the contract, your contract is posted on the Solidified Bounty platform where it is public to the entire Solidified community.

There are also a number of free resources available to help you keep your smart contract secure.

Free Auditing Resources

There are a variety of smart contract auditing resources available online for free. Echidna is a popular tool from Trail of Bits that provides unit tests and allows you to write tests for your contract. Trail of Bits also provides a long list of resources for smart contract auditing.

Check out the list of security tools from ConSensys.

You can use these free online resources to start auditing your own contracts internally before you get an external audit. Let’s learn about other ways to grow your skills in the next section.

Growing Your Auditing Skills

If you want to become an expert auditor, there are many ways to work on your auditing skills.

Solidified offers a bug bounty program where you are able to interview, and if accepted, you can become a professional auditor at Solidified. You’ll be given access to the bounty platform, and be given the opportunity to audit contracts and reap the rewards if you can find a security vulnerability in the contract.

Solidified also offers an auditing course. They’ve partnered with another company, B9 Academy, to offer a course on auditing. At the time of this writing, Solidified also offers a paid internship to the individuals who have the highest scores upon completion of the course. The course will walk through common security vulnerabilities in smart contracts, how to write an audit, how to start a career as an auditor, how to fix security issues, and more.

Summary

In this chapter, we discussed the different ways in which our smart contracts could be susceptible to vulnerabilities. We learned about a few different types of attacks, such as reentrancy and integer underflow. Securing your smart contract is important because a hacker can exploit your contract and freeze funds or steal them.

Auditing a contract externally is vital to the security of your smart contract. In this chapter, we discussed how you can find an external auditor or use Solidified, a site where you can get your contract audited and posted to a large community of bounty hunters. We also learned how we can audit a smart contract and the various free resources available.

We created this book to help you take your first steps into smart contract development—from building smart contracts and deploying them to connecting your contract to a frontend. This book can be used as a framework to expand on the contracts and code we provide to allow you to write your own DApps.

Best of luck on your smart contract development adventure!

..................Content has been hidden....................

You can't read the all page of ebook, please click here login for view all page.
Reset