👓Examples

Understanding the Examples: In the "Examples" section, we will dive into practical instances of vulnerabilities related to the use of .call, .transfer, and .send methods, sourced from actual bug bounty reports, Code Arena analyses, and beyond. By examining these real-world cases, you'll attain a richer and more concrete comprehension of the nuances associated with these Ethereum functions and their potential ramifications. This hands-on insight will significantly augment your smart contract auditing prowess.

Example 1: High vulnerabilty Code4rna bounty payout

Description:

In this example contract's operations, when utilizing a wrapped native token, employ the payable.transfer() method to manage complete user withdrawals. This approach introduces a risk due to the inherent limitations of the transfer() method. Specifically, transfer() has a hardcoded gas budget, which can cause the call to fail, especially when the recipient address is a smart contract. Consequently, any programmatic use of this contract becomes vulnerable.

Implications:

If a user fails to implement the necessary payable fallback function, or if the total gas consumption for the sequence of functions invoked during a native token transfer surpasses the 2300 gas limit, there are notable issues:

  • The native tokens won't be delivered successfully.

  • The mechanism to return the undelivered funds to the user will continuously fail. Notably, the closeTrade function in OpenLevV1 is affected, resulting in potential scenarios where users' primary funds are frozen. This elevates the vulnerability to a high-severity level.

relevant code:

    function doTransferOut(address to, IERC20 token, address weth, uint amount) external {
        if (address(token) == weth) {
            IWETH(weth).withdraw(amount);
            payable(to).transfer(amount);
        } else {
            token.safeTransfer(to, amount);
        }
    }

A safer approach would be to replace the transfer() function with the call method, which doesn't have the same restrictive gas limitations and offers more flexibility.

Example 2: Low vulnerabilty Code4rna bounty payout

Description:

The code in question makes use of the payable.transfer() method, a practice that is widely discouraged due to potential risks. The primary concern is that payable.transfer() can result in funds becoming inaccessible or "locked". The transfer() method necessitates that the recipient has a payable fallback function and allocates only 2300 gas for its execution. This limited gas budget can cause the transfer to fail in various scenarios:

  1. If the contract lacks a payable callback.

  2. When the contract's payable callback exceeds the 2300 gas limit. This could happen even with a simple event emission, which requires more than 2300 gas.

  3. If the contract is accessed through a proxy, and that proxy consumes the allotted 2300 gas

relevant code:

payable(msg.sender).transfer(amount);

Implications:

The usage of transfer() in this context can lead to inadvertent fund lockups if the recipient contract doesn't adhere to the expectations set by the 2300 gas stipend. In worst-case scenarios, users or contracts could permanently lose access to their funds.

Recommendation:

To ensure more reliable fund transfers and to avoid the mentioned pitfalls, it's advised to use the more flexible address.call{value: x}() pattern. This approach doesn't impose the same restrictive gas limitations as transfer() and offers better handling of different contract interactions.

Last updated