How to Secure Your Smart Contracts (Part 2)

Arithmetic overflows and underflows

Mar 26 · 3 min read

Prerequisites: A basic understanding of the Ethereum blockchain and smart contracts.

Introduction

Here, we’ll go through arithmetic overflows and underflows, a type of logic weakness that can sometimes creep into our code. We’ll describe what they mean, examples of how they might appear, and how to prevent them from appearing in our smart contracts.

What Are They?

Ethereum Virtual Machine (EVM) integers are always of a fixed size. For example, `unit8` can only store values between (and including) 0 and 255. Trying to store the value 256 in a `uint8` variable will result in a value of 0. This is ripe for exploitation if no checks are made before execution.

Underflows

`uint8 myValue = 2;uint8 subValue = 3;uint8 result = myValue - subValue;`

Here, our `result` variable would not equal -1 as we think it should; `result` would equal 255.

Counting backward from 2, the EVM goes 1 … 0 … 255. This is known as an underflow.

Overflows

`uint8 myValue = 254;uint8 addValue = 3;uint8 result = myValue + addValue;`

Here, our `result` variable does not equal 257 as we think it should. It gets calculated to 1 because 255 is the maximum value of `uint8`.

Counting upward from 254, the EVM goes … 255 … 0 … 1. This is known as an overflow.

Example

`mapping(address => uint) balances;function withdraw(uint _value) external {    require(balances[msg.sender] - _value >= 0);    balances[msg.sender] -= _value;    msg.sender.transfer(_value);    }`

The `require` statement requires that the balance of the sender minus the withdraw value is greater than or equal to 0. This makes sense to us logically since we don’t want to allow anyone to withdraw more than they have in their balance. However, this is vulnerable to underflow exploitation.

If the attacker had two Ether stored in the contract and attempted to withdraw ten, the `require` statement would allow it. This is because the subtraction would cause the result of `balances[msg.sender] — _value` to be greater than or equal to 0 by underflowing to the maximum. Because the integer is unsigned, the `require` statement will always pass as long as the `msg.sender` has a balance.

By this logic, anyone who has deposited a balance at any point can completely rinse the contract of funds.

Preventative Measures

The library is thoroughly scrutinized by the community before being released, so we can have confidence in its ability to prevent arithmetic logic bugs. Here’s an example of how to use SafeMath’s `sub()` function instead of using the minus arithmetic operator in our smart contract:

`using SafeMath for uint;function withdraw(uint _value) external {    require(balances[msg.sender].sub(_value) >= 0);    balances[msg.sender] -= _value;    msg.sender.transfer(_value);    }`

In this scenario, the `require` statement would fail if we attempted to withdraw more than our balance. This is because the `sub()` function requires the balance be greater than or equal to `_value`.

Written by

More From Medium

Should You Learn VIM as a Developer in 2020?

Mar 19 · 4 min read

Fun Side Projects That You Can Build Today

Mar 11 · 6 min read

The Zero-Dollar Infrastructure Stack

Mar 20 · 5 min read

1.1K

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