6 Solidity Smart Contract Security Best Practices
Blockchain's distinguishing characteristic, smart contracts, allows it to function as more than just a decentralized financial system and a trustless store of value. However, security is a challenge that needs to be solved in a fundamentally new way if blockchain is truly the technology that shifts paradigms.
Technically, smart contract security operates using the same principles as software security. A secure application's very first step begins with the code itself because if the code was not written using best programming techniques the attack surface of a prospective attacker increases.
This article will focus on explaining smart contracts security, providing a list of patterns and mistakes you should avoid to ensure your Solidity smart contracts code is more secure.
What is smart contract security?
On the Ethereum network, smart contracts are in place to manage and execute the blockchain operations that occur when users (addresses) interact with one another. Smart contracts are especially useful when there is a transfer or exchange of funds between two or more parties. Smart contracts increase transparency while decreasing operational costs, and they can also increase efficiency and reduce bureaucratic costs, depending on how they are implemented.
Smart contract security refers to the security guidelines and best practices developers, users, and exchanges apply when creating or interacting with smart contracts. Security entails developers examining their code, paying attention to common Solidity mistakes, and guaranteeing that a dapp's security is robust to be mainnet-ready.
Why is security important to developers?
With vast amounts of value transacted through or locked in smart contracts, they become attractive targets for malicious attacks from hackers. Minor coding errors can lead to huge sums of funds being lost. Since blockchain transactions are irreversible, making sure that a project's code is secure is essential. Blockchain technology's highly secure nature makes it difficult to retrieve funds and resolve issues hence, securing your smart contract.
1. Use Delegatecall Carefully
Delegatecall is identical to a message call except that the code at the target address is executed in the context of the calling contract and the values of msg.sender and msg.value are not changed.
Delegatecall has been extremely useful because it serves as the foundation for implementing libraries and modularizing code. Delegatecall also allows a contract to dynamically load code from a different address, however it introduces vulnerabilities because a contract essentially allows anyone to do anything they want with their state resulting in unexpected code execution.
In the example below, when contract B executes the delegatecall function to contract A, the code of contract A is executed but with contract B’s storage.
delegatecall affects the state variables of the contract that calls a function with delegatecall. The state variables of the contract that holds the functions that are borrowed are not read or written.
2. Use a Reentrancy Guard
Reentrancy is a programming method where an external function call causes the execution of a function to pause. Conditions in the logic of the external function call allow it to call itself repeatedly before the original function execution is finished.
A reentrancy attack takes advantage of unprotected external calls and can be a particularly damaging exploit, draining all of the funds in your contract if not handled properly.
Here is a simple example of a contract that is susceptible to re-entrancy:
A reentrancy guard is a modifier that causes execution to fail whenever a reenterancy act is discovered. This also prevents more than one function from being executed at a time by locking the contract.
3. Use msg.sender Instead of tx.origin for Authentication
In Solidity, tx.origin is a global variable that returns the address of the account that sent the transaction. Using the tx.origin variable for authorization may expose a contract to compromise if an authorized account calls into a malicious contract.
Avoiding the use of tx.origin for authentication purposes is the best method to guard against tx.origin attacks instead use msg.sender in its place.
The difference between tx.origin and msg.sender is msg.sender, the owner, can be a contract while tx.origin the owner can never be a contract.
Implementing msg.sender here:
4. Properly Use Solidity Visibility Modifiers
Function visibility can be set to be either internal, external, private, or public. It's crucial to think about which visibility is appropriate for your smart contract function.
Here is a brief description of each visibility modifier:
- Public - can be accessed by the main contract, derived contracts, and third party contracts
- External - can only be called by a third party.
- Internal - can be called by the main contract and any of its derived contracts
- Private - can only be called by the main contract in which it was specified
A developer's failure to utilize a visibility modifier frequently results in smart contract attacks. The function is thus by default set to be public, which may result in unintentional state changes.
5. Avoid Block Timestamp Manipulation
Block timestamps have been used historically for a number of purposes, including entropy for random numbers locking funds for a set amount of time, and different state-changing, time-dependent conditional statements. Because validators have the capacity to slightly alter timestamps, using block timestamps wrong in smart contracts can be quite risky.
The time difference between events can be estimated using block.number and the average block time. However, because block times can change and break functionality, it's best to avoid its use.
6. Avoid Arithmetic Overflow and Underflow
An integer would automatically roll over to a lower or higher number in Solidity versions prior to 0.8.0. If you decremented 0 by 1 (0-1) on an unsigned number, the outcome would simply be: MAX instead of -1 or an error.
The easiest way is to use at least a 0.8 version of the Solidity compiler. In Solidity 0.8, the compiler will automatically take care of checking for overflows and underflows.
3 Popular Smart Contract Security Tools
Three of the most popular smart contract security tools are Slither, Mythril, and Securify.
Slither is a static analyzer that features more than 40 built-in vulnerability detectors for widespread flaws. Slither executes a number of vulnerability scanners, outputs visual information about the terms of the contract, and offers an API for quickly creating unique studies.
This amazing security tool gives developers the ability to identify vulnerabilities, improve their understanding of the code, and quickly prototype unique analysis.
Mythril is an open-source element of MythX and an EVM bytecode security analysis tool that supports smart contracts created for the Tron, Roostock, Vechain, Quorum, and other EVM-compatible blockchains.
Securify is a smart contract vulnerability checker supported by the Ethereum Foundation and ChainSecurity. This well-known Ethereum smart contract scanner employs context-specific static analysis for more precise security assessments and can find up to 37 smart contract flaws.
Secure Your Smart Contracts
The future of blockchain technology is dependent on the developers who work on it. Because smart contract security is widely perceived as blockchain security, the actions of independent developers influence public perception of the blockchain. When creating smart contracts, project teams must consider proper security best practices.