Skip to main content

Checks Effects Interactions

Description

The improper check-effect-interaction pattern occurs when a contract performs checks, such as require statements, then carries out state-changing operations, and finally interacts with another contract or external account. This creates a vulnerability where the state of the contract can be manipulated between the check and the interaction, allowing malicious actors to exploit the vulnerability and potentially cause significant damage.

Example Code

Consider a scenario where a contract allows users to stake tokens in exchange for rewards. The contract first checks that the user has enough tokens to stake, then transfers the tokens to the contract, and finally updates the user's staked balance and rewards:

function stake(uint256 amount) external {
require(token.balanceOf(msg.sender) >= amount, "Insufficient balance");
token.transferFrom(msg.sender, address(this), amount);
userStakes[msg.sender] += amount;
userRewards[msg.sender] += calculateRewards(msg.sender, amount);
}

However, if the token transfer fails (e.g., if the user doesn't have enough tokens or if the token contract reverts), the user's staked balance and rewards will still be updated.

To avoid this issue, the state changes should be performed before the transfer:

function stake(uint256 amount) external {
require(token.balanceOf(msg.sender) >= amount, "Insufficient balance");
userStakes[msg.sender] += amount;
userRewards[msg.sender] += calculateRewards(msg.sender, amount);
token.transferFrom(msg.sender, address(this), amount);
}

Now, if the token transfer fails, the state changes will not be applied, and the user's staked balance and rewards will remain unchanged.

Recommendation

Always perform state changes before interactions to avoid unexpected behavior and vulnerabilities caused by the CEI anti-pattern.