Smart contracts are deemed “smart” because the programs are self-executable, purely from written code. But how smart are they really?
Just like in other software programs, the strength of the program depends on the quality of the source code. After conducting numerous smart contract audits across a multitude of use cases, we’ve compiled a few common–and sometimes fatal–vulnerabilities that you’ll want to remember in order to prevent some not-so-smart contract bugs.
(For more information on smart contracts, check out our Smart Contracts 101.)
In these types of vulnerabilities, a malicious miner manipulates variables that exist outside of the execution of the smart contract in order to work the system in their favor.
Believe it or not, the timestamp of a transaction can be influenced by miners to some degree. Because there’s no centralized clock that all the nodes in a network adhere to (when a block is successfully mined on a node, the timestamp is set locally), there’s typically a tolerated drift, or variance, when new blocks are added.
While that variance is not usually a cause for concern, that opening may still provide opportunities for a miner to manipulate the timestamp of the block being generated in order to give them a more favorable outcome.
Warning on using block.number to keep time
Block number dependence is not so much a vulnerability than it is a loose workaround or alternative to using
block.timestamp. Though the
block.number function itself can’t be manipulated by design, it’s not always a reliable variable when calculating time that’s far in the future–because the time it takes to mine a block can vary and change.
Let’s say, for example, one of the rules on a smart contract is to transfer assets from User A to User B in 365 days, and it takes, on average, 10 minutes to mine a block. One might try to create a rule with
block.number to transfer those assets when the
block.number variable reaches 52,560 (365 days x 24 hours/day x 60 minutes/day / 10 minutes of mining/block).
But! If the average time to mine a block changes for whatever reason (e.g., if nodes drop off the network, causing the difficulty and time to mine blocks to decrease), the asset transfer may happen too soon or too late.
How do you know whether to use it or not? Depending on the exact use case of a smart contract,
block.number could be the right workaround if time sensitivity isn’t a concern or if you’re looking at a relatively short-term contract length. However, if the smart contract needs to execute at a specific date and time–especially if it’s well into the future–then the lack of accuracy is enough of a reason to look for another solution.
A race condition vulnerability that occurs when code depends on the order of the transactions submitted to it.
Because the order of the transactions are known before they’re executed on and added to a block, this may provide too much transparency for some decentralized markets. For example, if this type of vulnerability were applied to the stock market, you would be able to see who is buying and selling shares before it even happens!
Depending on the protocol of the chain you’re working in, this ordering may result in an unbalanced and unfair market. On Ethereum, for example, a malicious miner could pay more gas to prioritize their transaction over another’s so that theirs is processed first–opening the door for bad actors to cut the line.
If your smart contract relies on transactions to execute in the order they’re received, make sure you build in safeguards so that bad actors can’t manipulate that order.
An overflow occurs when arithmetic operations causes an output that’s larger than the maximum size allocated to it; conversely, an underflow occurs when the output is smaller than the minimum size.
A classic example of integer overflow is when a car’s odometer is unable to capture mileage beyond 999999 (6 digits worth). When that happens, the car’s odometer inaccurately represents this new seven-figure number (1,000,000) as the last six digits (000000), even though the actual mileage is 1000000 (7 digits long).
Because you want data to be represented as accurately as possible, failure to account for overflows and underflows would result in inaccurate data and unintended outputs from the smart contract.
When you’re reviewing thousands of lines of code, it can be easy to overlook these vulnerabilities, but it’s crucial to account for them. One way to be fully confident that your code is safe from overflows and underflows is to use Formal Verification to apply mathematical proofs that calculate whether those computations could ever exist.
Because of the nature of public blockchains, access and privilege controls may not be important or even necessary to set; anyone can read or write blocks into the chain. However, as the use of blockchain technology evolves and expands, permissioned and private chains will require deeper consideration and robust access levels.
If a blockchain does not have any access controls set, it’s public by default which may cause issues for stakeholders. If you have a need to control the nodes that come into the network or restrict blocks that may house sensitive information, you should be aware of the access you’re offering.
Though it’s thought that blockchains may be the answer to mitigating traditional DDoS attacks, blockchains themselves can still fall victim. If protective measures aren’t built into the chain protocol itself, blockchain nodes are vulnerable to being flooded with traffic from maliciously coded smart contracts. This would cause all of the network’s processing resources to divert to handling the overload, which in turn would render the network unusable.
While no major DDoS attack on a blockchain network has been reported yet, TRON revealed earlier this year a vulnerability that put the entire network at risk.
Attacks by way of smart contracts aren’t always external; a malicious contract may call back into the calling contract before the first function is finished.
In other words, a malicious smart contract is coded into the victim smart contract so that when the victim contract executes for the first time, the malicious contract can interrupt the first execution by either calling the victim contract again (with a fallback function) before its completion or by running a whole new function altogether. It’s deemed reentrancy because it enters another call of the contract again, before the initial call is finished.
This kind of loophole allows for malicious users / contract writers to overtake the original intention of the victim’s smart contract and game the system in their favor–creating a sort of internal attack. A well-known example of this vulnerability was the DAO hack, which caused the hard fork between the Ethereum and Ethereum Classic chains.
While we won’t speak about it at length here, if you need a primer or refresher on Formal Verification, our Introduction to Formal Verification may be helpful.
The concept of Formal Verification isn’t new (it’s been used for mission-critical projects like the NASA Mars Rover and drone technology), but the team here at CertiK uses cutting-edge technology to help protect blockchains and smart contracts. By representing every single possible variable in a smart contract with mathematical proofs, the source code can be proven or disproven as to whether it was built exactly as intended.
Various tools can be used to check for errors, but the limitation is that they often miss things (and you can’t decipher whether the lack of finding something means that it’s safe or that the tool missed something). Without comprehensive mathematical proofs, evidence of full functional correctness is not possible.
It’s important to invest in security, particularly with blockchains. Once a smart contract is deployed, changes are difficult and transactions are permanent, which is both the beauty and the risk of blockchain technology. That said, the CertiK team can provide a comprehensive audit of your smart contracts and give you the peace of mind that they’re as secure as possible before deployment.
Don’t just look for bugs–prove that they cannot exist.