5. Testing Control Flow
A normal EVM blockchain transaction can only call one function at the receiver address, and the executions will be determined by the code stored in the address. The outcome of each function depends on the input (the user’s input parameters and the world states); if the contract blindly trusts the user’s parameters, the user could manipulate the execution flow.
5.1. Reentrancy
If the contract has an external call (we usually call this as callback) to an address that the user can control, the user could call this function again or other functions in the execution flow (re-entrant). This could cause a problem if the contract does not expect their states to be changed while calling an external address. A state check can be done after the call to guarantee the invariance of the contract or by applying check-effect interactions.
Testing
5.1.1 Reentrant calling should not negatively affect the contract states
Reentrancy can happen when an external contract is invoked, allowing that contract to control the execution flow and perform reentrant calls. If the states are not completely updated or resolved, reentrancy can affect the invariant of the states.
The test can be done by checking the following condition:
Check that external calling is not done to unknown contracts
Check that measures are implemented to prevent reentrancy
Check that there are invariant validation after the reentrant
For example, the withdrawAll()
function has a reentrancy issue because it gives the external call to the user (msg.sender
), and the state is updated (balance[msg.sender] = 0
) after the external call.
When the call()
function is executed, if the fallback()
function is implemented on the msg.sender
address, the fallback function will be called before the balance[msg.sender]
is set to 0
. This can be used for reentrant calls to the withdrawAll()
function for draining all tokens in the contract.
5.2. Input validation
The parameters of a function are the parts over which the user has control. The result of the function would normally depend on the input parameters. If the user can craft input parameters to the function that do not properly handle the parameter, e.g., a zero value, a negative value, or a blank array, the user could break an invariant of the contract.
Testing
5.2.1. Lack of input validation
If the function can only operate within some range of input, the contract should always validate the user input, which could invalidate the platform's invariants.
For example, the function below can deposit multiple tokens in one execution, and the caller will get a free gift from the contract. But the user can send a blank array as the input to bypass the depositing and get the gift freely.
Checklist
Reentrant calling should not invalidate the platform's invariants
The function inputs should always be validated
Last updated