Skip to content

Latest commit

 

History

History
102 lines (73 loc) · 5.66 KB

059.md

File metadata and controls

102 lines (73 loc) · 5.66 KB

Nice Laurel Turtle

High

Unchecked Return Value in transferAndCall Function Allowing Silent Transfer Failures

Summary

The transferAndCall function in the MorphStandardERC20 smart contract fails to verify the return value of the ERC20Upgradeable.transfer call. This omission leads to scenarios where token transfers can silently fail without reverting the transaction. As a result, the function continues its execution under the false assumption that the transfer was successful.

Vulnerability Detail

The MorphStandardERC20 contract implements a key feature of ERC677, specifically the transferAndCall function. This function is an implementation of the ERC677 token standard, which extends ERC20 to allow token transfers to trigger a function call on the receiving contract.

function transferAndCall(address receiver, uint256 amount, bytes calldata data) external returns (bool success) {
    ERC20Upgradeable.transfer(receiver, amount);
    if (isContract(receiver)) {
        contractFallback(receiver, amount, data);
    }
    return true;
}

Key points:

  • It allows tokens to be transferred to a contract.
  • The function first performs a standard ERC20 transfer.
  • It then checks if the receiver is a contract.

If so, it calls contractFallback, which invokes the onTokenTransfer function on the receiving contract. The contract focuses on the functional aspect of ERC677 rather than full compliance with the standard.

The transferAndCall function in the MorphStandardERC20 smart contract fails to verify the return value of the ERC20Upgradeable.transfer call. This omission leads to scenarios where token transfers can silently fail without reverting the transaction. As a result, the function continues its execution under the false assumption that the transfer was successful.

ERC20Upgradeable.transfer(receiver, amount);

This line attempts to transfer amount tokens from the caller to the receiver. The transfer function returns a boolean (true for success, false for failure). The function always returns true, indicating a successful operation. In this, the return value of the transfer function is not captured or checked.

If the transfer fails (e.g., due to insufficient balance), transfer returns false. Since the return value is ignored, the function does not detect whether the transfer succeeded or failed.

return true;

No matter what happens with the transfer, the function always returns true. This misleads users and integrators into believing that the transfer was successful, even when it wasn't.

PoC

Alice has 100 tokens, while Bob has 0 tokens. When Alice tries to transfer 200 tokens to Bob using the transferAndCall function with the command

await token.transferAndCall(bob, 200, "0x", { from: alice });

The expected outcome is that the transfer should fail since Alice only has 100 tokens.

In this case, the function should revert or return false to indicate the failure. However, the actual behavior shows that the transfer function fails and returns false, but this return value is ignored, causing the contract to execute contractFallback anyway. As a result, the function returns true despite the transfer failure. Consequently, Alice's balance remains at 100 tokens, Bob's balance remains at 0 tokens, and the misleading success message indicates that a transfer occurred successfully.

This is how the Output shows:

Alice attempts to transfer 200 tokens to Bob using transferAndCall.
transferAndCall transaction returned: true
Alice's balance: 100
Bob's balance: 0

Impact

  1. Users might think they've successfully transferred tokens when they actually haven't, which can cause financial issues and lead to a loss of trust.
  2. Attackers could create contracts that take advantage of this behavior, allowing them to change contract states or get around the intended rules.
  3. Decentralized Applications (DApps) and other smart contracts that depend on the successful execution of transferAndCall may behave unexpectedly or fail, which can disrupt user experiences and functionalities.

Code Snippet

https://github.com/sherlock-audit/2024-08-morphl2/blob/main/morph/contracts/contracts/libraries/token/MorphStandardERC20.sol#L58-L64

Tool used

Manual Review

Recommendation

  1. Replaces the direct ERC20Upgradeable.transfer call with SafeERC20.safeTransfer
 import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";
   using SafeERC20 for IERC20;
   IERC20(address(this)).safeTransfer(receiver, amount);
  1. Uses require to ensure that the transfer was successful, reverting the transaction if it wasn't.
     function transferAndCall(address receiver, uint256 amount, bytes calldata data) external returns (bool success) {
         bool sent = ERC20Upgradeable.transfer(receiver, amount);
         require(sent, "Token transfer failed");
         if (isContract(receiver)) {
             contractFallback(receiver, amount, data);
         }
         return true;
     }