The smart contract is now a key instrument in the blockchain ecosystem. Nevertheless, sometimes unanticipated errors in smart contracts are exploited by cyber-attackers regularly. Thus, the adoption of contracts requires solid security guarantees. Yet, it is difficult to create programs free of bugs. Here’s when an Ethereum contract audit checklist will be handy, ensuring that the auditor doesn’t miss out on anything vital. So, here’s our audit checklist.
Step-by-Step Smart Contract Security Audit Checklist
The smart contract audit process may be divided into distinct phases with seasoned auditors at the helm. Far-reaching contract audit checklists guarantee that an audit is carried out systematically. The smart contracts creation standards, including the ERC20 audit, may help understand the topic. Our checklist is based on the study results of the leading security of blockchain investigation teams in the execution process.
Coding Specification Issue
(1) Compiler version
The so-called compiler version needs to be indicated in the smart contract code. Therefore, please use the latest version.
Earlier compiler versions may cause several security issues. V0.4.23 updates security vulnerability. In this version, if both constructors are applied, i.e.,
One of the constructors will be overlooked, which only affects v0.4.22. However, V0.4.25 successfully fixes the uninitialized storage pointer issue.
(2) Constructor writing issue
The proper constructor has to be used for several compiler versions; if not, the contract owner can change.
In the rules of solidify compiler syntax of versions less than 0.4.22, the constructor should be equal to the contract name, e.g.,
Following the 0.4.22 version, the keyword was generated as a constructor’s declaration. Yet here, no function is required.
If you do not follow the corresponding tactics, the constructor may be compiled into a normal function, leading to grave outcomes like owner permission.
(3) Return standard
After the ERC20 specification, transfer plus approve functions are supposed to return a bool value and add a return value code.
The result of transferFrom should be consistent with the transfer result.
(4) Event standard
You should fFollow the ERC20 specification and necessitate the transfer of the function with approval to trigger the corresponding event.
(5) Issue of fake recharge
In the transfer function, the transfer amount and balance judgment need to use the essential procedure to throw an error, or the system will mistakenly judge the transaction as efficient.
Design defect issue
(1) Approve authorization function test
In a contract, conditional competition needs to be avoided in the approve function. Therefore, before you modify the allowance, it is better to change it to 0 first and then to _value.
The cause for this vulnerability is to encourage miners to hash in the underlying agreement. Unfortunately, the miners typically prefer to package the deals with higher gas costs to make more profits.
By setting 0, the risks can be mitigated to some extent.
The code written above may lead to this conditional competition.
So add the code in the approve function:
Change that allowance to 0 and next, the corresponding number.
(2) Loop DoS issue
[1] Loop security issue
The attacker may enter an excessive loop to perform the Dos attack. When you need to transfer funds to multiple accounts simultaneously, traverse the transfer of the target account list. That may open the system to DoS attacks.
[2] Loop consumption issue
Overall, it is not advised to use too many loops in contracts.
The actual gas consumption depends on the transaction’s complexity. The greater the number of loops, the more complex the transaction is. The operation fails when the maximum acceptable gas consumption surpasses the limit.
In that situation, please use withdrawFunds to allow the user to retrieve their coin instead of transferring it to the corresponding account. That can reduce the risk to a certain extent.
Issue of Coding Security
(1) Issue of Overflow
[1] Arithmetic overflow
When calling multiplication, division, addition, and subtraction, you need to use the safeMath library; otherwise, it will eventually lead to calculation overflow, namely, unpreventable loss.
[2] Coin/destroy overflow issue
In the function named coin/destroy, the upper limit must be set for totalSupply to avoid the growth in malicious coinage occurrences due to weaknesses such as arithmetic overflow.
In the above code, there is no limit to totalSupply, which may lead to exponential arithmetic overflow.
Here is the correct form:
(2) Reentrancy vulnerability
Avoid applying call to trade in contracts to avoid reentrancy flaws.
In smart contracts, send, call, and transfer are offered to trade ETH. The key difference for calls is — no limit for gas. When the gas is insufficient, the others will report out of gas.
The above code shows a demo of the reentrancy vulnerability. Such weaknesses recursively transfer a huge number of contract coins.
For potential reentrancy issues, use the transfer function to finalize the transfer or limit the call’s gas execution. These can minimize the harm.
The code shows how to use mutex lock to evade recursive protection.
(3) Call injection
When a call function is caused, strict permission control should be made, or the function stimulated to hardcode directly should be written.
In the EVM design, if the call’s parameter data is 0xdeadbeef + 0x0000000000…..01, then it is the invoke function.
The call injection may cause token theft and permission bypass. Private and even partially advantageous activities can be called via call injection.
(4) Permission control
Various functions in the smart contract should have appropriate permission settings.
You should check whether the functions listed in the smart contracts properly use private, public, and other keywords for visibility modification. To prevent unauthorized deployment, see whether it is precisely defined and restrict the entrée to key functions.
The code should not be a public function.
(5) Replay attack
If a smart contract demands trusted management, spot the verification non-reusability to avoid attacks classified as a replay.
In the asset management system, there are frequent cases of entrusted management. For instance, the principal gives the digital assets to the trustee and pays a set fee to them. Here, the transferProxy function is applied when user1 transfers a coin to user3 but does not have enough ETH to pay for the gas price.
Consequently, replay attacks may be performed with other variables constant, leading to multiple transfers.
Issue of coding design
(1) Issue of address initialization
When the address is used in a function, add the verification of require (_to!=address(0)) to avoid unwanted loss.
The address initialized by EVM when compiling the contract code is 0. If the programmer initializes the code’s address variable not set an initial value, unwanted security risks may arise.
(2) Balance judgment
Do not suppose the contract is generated with a balance of 0, and the transfer can be forced.
Please write invariants for testing account balances, as an attacker can forcibly transfer Wei to any user account, albeit the fallback function throws.
The hacker creates an agreement with 1wei and then calls the so-called selfdestruct(victimAddress) to crash it. This balance is transferred to the target forcibly, and the contract has no executable code, so it cannot be blocked.
(3) Judgment function issue
The require function is applied when the so-called conditional judgment is used instead of the assert function. However, because the remaining gas is consumed, it is consistent in other areas.
(4) Transfer function issue
It is correct to use transfer before finalizing a transaction instead of sending the funds by default.
When the send or transfer function’s target is a contract, its fallback function is stimulated. Yet if the fallback function fails to run, the results will be error and roll back, and send will appear incorrect. Hence, when using send, you should judge the return type.
The code uses the function send() to transfer, as there is no testing for the returned value of this function.
If msg.sender fails to call the user account fallback(), send() returns as false, which results in a decrease in the account balance with funds loss.
(5) Error handling
When the contract requires a call or other methods that run at the base level of the address function, make logical error handling.
If such an operation results in an error, it will return false and keep the execution process.
So when applying the mentioned method, please check the return value and perform error handling.
Before calling such functions, you should check the address validity.
(6) External call design issue
Here, pull is preferred instead of push for an external contract.
In the case of external calls, unexpected failure takes place. Hence, varying external operations to the user’s disposal will help avoid loss.
Error sample:
When a so-called transfer to a party is necessary, the transfer is altered to define the withdraw function, letting the owner execute the process and cash out the balance. That will help avoid unexpected losses.
Sample code:
(7) Poor random number issue
The random numbers strategy requires consideration.
The Fomo3D contract presents the block info as a parameter for producing the random number seed within the airdrop reward. Unfortunately, that causes the impact on seed only by the contract address and cannot be fully random.
The code above directly led to the incident Fomo3D resulting in over a few thousand ETH loss.
Here is a reasonable method of RNG hash-commit-reveal, i.e., the user submits the action plan with a hash to the back end. In that way, the client doesn’t obtain that random number.
(8) Variable coverage vulnerability
Avoid the following key of the array variable in the agreement being controlled.
In EVM, arrays may differ from other types. As they are dynamically sized, array data is typically calculated as
That is a problem as the offset is manageable so that users may write values to any storage address.
That may affect the code’s logic and lead to more risks.
Code hidden danger
(1) Grammatical property issue
Be cautious with the rounding down of integer division.
In digital contracts, all integer divisions are rounded to the adjoining integer. For more precision, a multiplier is needed to raise this number.
If the issue occurs in the code, the compiler will spot an error and stop compiling. Yet, if it appears implicitly, the logical step is the round-down approach.
(2) Data reliability
The timestamp must not appear in the code to avoid the miner’s interference. Instead, stable data such as blocks.height needs to be used.
(3) Data privacy
Remember that all data in the blockchain is public.
All data comprising private variables are public. Therefore, privacy data cannot be stored within the chain.
(4) Contract users
Users should consider the case when the trading target is the contract to prevent malicious uses.
The contract shown above is a simple bidding code to compete. When the trade Ether is larger than the highestBid in the agreement, the real-time user will become the “king,” with the trading amount as the newer highestBid.
(5) Gas consumption optimization
For some variables and functions that do not involve state alterations, you can add a constant to avoid gas consumption.
(6) Log records
Please register key events in the Event record. To make maintenance, operation, and monitoring smoother, besides authorization, other processes need to add thorough event data.
(7) Fallback function
Define the contract’s Fallback function and simplify the procedure.
It will be called when a problem with the execution of the contract occurs. Only 2300 gas is used to conduct the fallback function after the failure when the send/transfer function is called. This needs to be cautiously written to avoid gas use.
See the examples:
(8) Owner permission issue
Avoid larger permissions.
For too hefty permissions, the user can freely modify any data in the contract, comprising arbitrary transfer, coinage, and modification regulations.
Note the requirements on this issue:
- Only the owner can withdraw the balance from the contract.
- After the contract creation, no one can alter the rules.
(9) Race condition issue
Avoid counting on the order of the transactions.
There is often a reliance on the sequence of transactions in contracts, for instance, the last winner rule. These rules are designed on constant dependence on order. So, avoid this problem.
(10) User authentication issue
Do not apply tx.origin for authentication.
Tx.origin is the initial address. For instance, if a user generates contract c via contract b, for contract c, tx.origin is user a, and msg.sender is contract b. That exemplifies a potential phishing attack that is unsafe for authentication.
See an example:
(11) Uninitialized storage pointer
Do not initialize struct variables in the following functions.
The local network variables are stored by default in storage or memory. Solidity allows pointers to display to an uninitialized reference, meaning variables point to other maintained ones. That also leads to dangerous outcomes like variable coverage.
- After the code above is compiled, s.x and s.y will incorrectly indicate the owner and a.
- After the hacker executes fake_foo, the owner changes.
- This issue was recorded in the latest version of 0.4.25.
FAQ
Ethereum contract audits allow businesses to identify and remove unnecessary security risks. Smart contract audits also prove a potential investment partner’s credibility.
Once your project’s smart contract developers complete the blockchain creation process, the best way to check it is to hire the services of an auditor, assigning the venture to them.
Audits are crucial for businesses because most contracts deal with digital assets or valuable items.
A crypto-audit performs a systematic application, system, or database analysis to test its security.