Check list of Vulnerabilities in Smart contracts
1) Solidity Version
Using a single solidity version in the suite of smart contracts.
2) Unlocked Pragma
The unlocked pragma can bring the risk of having different versions during testing and deployment. Hence don't use ^ in the version, better to lock to a specific version.
3) Incorrect Access Control
Implementing access control to functions in a smart contract is extremely critical. Clearly defining the scope and restrictive access to functions that modify the state is of paramount importance. Each state-changing function should be attached with the correct modifier.
4) Withdraw funds
Smart contracts that can accept funds should have a provision to withdraw funds. Such withdrawal functions should be properly protected with access control via modifiers/scope of visibility.
5) Unprotected call to self destruct
The use of self-destruct removes the byte code of the smart contract making it a dead code. Therefore, the such function should be protected.
6) Modifiers' Side effects
Modifiers should only play the role of access control and data validation, but should never make any state changes. Modifiers should revert or execute the function code, but should not return values for further testing by the caller.
7) Mis-spelled constructor
Earlier versions of solidity supported constructors with the same name as the contract. Incorrect naming could result in it becoming a public function creating vulnerability.
8) Delegate Call
Delegating calls transfers the logic flow to another contract while the context is attached to the calling contract. If a delegate call is made to a malicious contract, the malicious contract gets access to the storage of the calling contract and hence could be manipulated.
9) Reentrancy
A smart contract can call another smart contract, and this process creates opportunities for the smart contract to enter the calling contract many times. This is a huge vulnerability. It is important to follow the CEI pattern to protect from such vulnerability.
CEI - C - checks for the valid state to enter
E - effects is the change in the state of the contract
I - interaction where call flows to another contract
10) ERC777 reentrancy
Hooks that the contract supports can cause reentrancy
11) Transfer & Send
These functions are thought out as reentrancy mitigation as these come with a limited supply of gas. But the risk comes with upgrades in infra which will make such contracts non-functional.
12) Private data
Data on the blockchain is not private. The access specifier prevents smart contracts from accessing private variable values, but this data itself can be accessed by anyone. Hence anything secret should not be stored on the blockchain.
13) Blockchain is deterministic
Blockchain is deterministic and hence there is no good way to generate randomness in the blockchain directly. Many times block timestamp is presumed to be random, which is not the case. Any algorithm on top block timestamp or block number is computable.
14) Front running
Users can submit transactions to the blockchain, but processing them is not entirely dependent on the order of submission. As such, the transaction sitting in Mempool are vulnerable to front running.
For example, a transaction submitted by the user solving a puzzle for a reward can be front run by another user who is watching the mempool. Such a user can submit another transaction with a higher gas price and claim the reward before the original user.
15) ERC20 Approve to spend on Behalf
There is a potential to front-run the approval and spend the funds in case the second approval was downgrading the previously approved amount.
example: B has the approval to spend 100 tokens of A. A has decided to downgrade such approval to 50 tokens. B notices that downgrade and spends 100 tokens using higher gas fees. Later B can again spend 50 tokens as well.
A intend to limit B to only 50 tokens, while B already spent 150 tokens.
Instead, use increase and decrease allowance.
16) signature
If a hacker can get access to v,r,s, then the hacker can create another signature without access to private keys.
17) ERC20 Transfer()
The transfer function should return a boolean value. Recent versions will revert the transaction.
18) Contract Balance()
Reliance on this. balance of a contract for its business logic can be dangerous. Contracts support to receive and fallback function can accept ether and could unexpectedly change the contract balances. This could break the contract logic.
Another way to break the contract logic is using self destruct way to fund the smart contract that does not have receive and fallback functions.
It is better to use a storage variable to track the balance and build business logic on that variable.
19) Strict Equalities
Using strict equalities in smart contract logic can be dangerous and could break the contract. For example, let's say, a contract sends the receiver a reward if the balance of the contract is 15 Ether. The deposit expects only 1 Ether per transaction.
If someone forces sent a 0.25 ether, this contract will never honour that condition.
20) Locked Ether
There is a function to transfer ether to a contract, but there is no mechanism to withdraw the funds from the contract. In such cases, the ether is locked permanently. It is important to evaluate if sufficient withdrawal mechanisms are provisions when receive and fallback functions are supported.
21) tx. origin
Tx.origin is susceptible to phishing attacks. For authorization, always use msg. sender only.
22)Contract vs EOA check
There are scenarios to check if the call is coming from an EOA or smart contract. In such cases, the size of the code is considered as a decision-maker. There are pitfalls to this, such as while in a contractor, the size of the code is 0. An attacker can attack the constructor of the attacking contract and bypass this check.
23) Mapping deleting struct
If a smart contract has mapping that has struct mapped to the key. Deleting struct will delete the struct, but the entry in the mapping remains and could cause unexpected code behaviour.
It is better to use a flag in the struct as deleted so that logic can ignore such entries in the map.
24) Checking return values
The caller should check for returned values and take action based on the returned values. This is especially necessary for low-level calls like send, call and delegate calls.
Some of these calls don't revert as their default behaviour, but instead, return a boolean to indicate success or failure.
25) Account existence
check if an account exists before transacting with them.
26) Shadowing variables
Having the same name state variable and local function variables can result in unexpected behaviour. It is better to follow a naming convention to identify the scope clearly in the logic.
27) Costly operations in loops
Costly operations, for example, calling another contract's function in a loop could be dangerous especially then the size of the loop is not known. This kind of logic can end up as a denial of service due to insufficient gas.
28) Block gas limit
There is a limit of 30M. In the case where there is logically written in loops if the gas consumption grows to the high need of a case, such code could be nominated as dead code.
Always evaluate loops and should be bounded to small numbers.
29) Events
Events should be emitted for all critical operations. This is the mechanism to notify the external world about the state change. Missing event for state change is a security concern as it affects off-chain monitoring.
30)Dangerous Unary Expression
Unary expressions are susceptible to bug incase the intention was to increment by one but instead was initialized to 1 by mistake by the developer.
x += 1
x =+ 1
31) Missing Zero address Check
All operations related to transferring funds should check for 0 addresses. once transfer, such funds are permanently lost.
32) Critical Address change
Any change in the address of a critical address should be a two-step approach.
Step 1 is to approve the change
step 2 update the new address. With this approach, there is a possibility to recover in case an incorrect address was passed.
33) function default visibility is public
Explicit visibility should be specified in the function signature to prevent an incorrect exposure of critical function.
34) Malleability risk from dirty high-order bits.
35) Assembly usage
using Assembly in solidity and bypassing the checks of the compiler could give undue power to the smart contract developer. Any code written in assembly attracts strict code review from audits.
36) Uninitialized state variables
State variables if not initialized could result in un expected behaviour of the smart contract.
37) Out-of-range Enum
38) unused code
Smart contracts should not have any unused code in the contract at the time of deployment.
39) Redundant code
The code should not be repeated. Any repetition is considered low quality.
40) ERC20 decimals
The number of decimals for ERC20 should be explicitly handled as the template provides an option to specify. Once the default is overridden, pay attention to ensure resolution is maintained in the contract.
41) ERC777 hook
ERC777 hooks are susceptible to re entrance attacks
42) Assert vs Require
Assert should be used to validate the code primitive and not the validation of data.
Require should be for validation of data.
43) Token is pausable by a single address
If a single address has the power to pause the contract. The funds will be locked and poses risk. A governance model is best advised.
44) Flash loan
Flash loans have been a source of many hack strategies in recent times. Always evaluate the risk related to a token manipulated in the context of a flash loan.
45) Token has only one address
46) Token is listed in one exchange
The token listed in one change is risky
47) Token price is sourced from one exchange
Soucing token prices from one exchange can lead to price manipulation using flash loans.
48) unprotected initializer in proxy
Initializer should be called only once in the proxy contract as the constructor will not be called. It is important to prevent the initializer be called more than once.
49) Dynamic array clean up
50) Token development team is unknown
If the token team members are not known, and don't have social media profiles and reputations, consider the project risky.