transmissions11/solmate

safeTransfer fails on flashloan callback for weird erc20

sandybradley opened this issue · 3 comments

Problem

safeTransfer fails on flashloan callback for weird erc20 tokens (Crv, Frax, Uni, Comp)

e.g. Frax

[376] 0x853d955aCEf822Db058eb8505911ED77F175b99e::transfer(0xeC8C342bc3E07F05B9a782bc34e7f04fB9B44502, 414930767680896959267)
└─ ← "EvmError: Revert"

e.g. Crv

[485] 0xD533a949740bb3306d119CC777fa900bA034cd52::transfer(0x58Dc5a51fE44589BEb22E8CE67720B5BC5378009, 16686103168043406255326)
└─ ← "EvmError: Revert"

e.g. UNI

0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984::transfer(0xDafd66636E2561b0284EDdE37e42d192F2844D40, 3645771712397557604)
└─ ← "EvmError: Revert"

Solution

mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.

changed to

mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append the "to" argument.
[2981] 0xD533a949740bb3306d119CC777fa900bA034cd52::transfer(OpenMevRouterWeirdFuzzTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], 3843801195941263362791)
├─ emit Transfer(from: 0x58Dc5a51fE44589BEb22E8CE67720B5BC5378009, to: OpenMevRouterWeirdFuzzTest: [0x7FA9385bE102ac3EAc297483Dd6233D62b3e1496], amount: 3843801195941263362791)
└─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
[3523] 0x853d955aCEf822Db058eb8505911ED77F175b99e::transfer(OpenMevRouter: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], 275257482344025778023)
├─ emit Transfer(from: 0xeC8C342bc3E07F05B9a782bc34e7f04fB9B44502, to: OpenMevRouter: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], amount: 275257482344025778023)
└─ ← 0x0000000000000000000000000000000000000000000000000000000000000001
[4862] 0x1f9840a85d5aF5bf1D1762F925BDADdC4201F984::transfer(0xd3d2E2692501A5c9Ca623199D38826e513033a17, 18084382183351755697339)
├─ emit Transfer(from: OpenMevRouter: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], to: 0xd3d2E2692501A5c9Ca623199D38826e513033a17, amount: 18084382183351755697339)
└─ ← 0x0000000000000000000000000000000000000000000000000000000000000001

code update:

// SPDX-License-Identifier: AGPL-3.0-only
pragma solidity >=0.8.17;

import { ERC20 } from "solmate/tokens/ERC20.sol";

/// @notice Safe ETH and ERC20 transfer library that gracefully handles missing return values.
/// @author Solmate (https://github.com/transmissions11/solmate/blob/main/src/utils/SafeTransferLib.sol)
/// @dev Use with caution! Some functions in this library knowingly create dirty bits at the destination of the free memory pointer.
/// @dev Note that none of the functions in this library check that a token has code at all! That responsibility is delegated to the caller.
library SafeTransferLib {

    error TransferFailed();
    error ApproveFailed();
    /*//////////////////////////////////////////////////////////////
                             ETH OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferETH(address to, uint256 amount) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Transfer the ETH and store if it succeeded or not.
            success := call(gas(), to, amount, 0, 0, 0, 0)
        }

        // require(success, "ETH_TRANSFER_FAILED");
        if (!success) revert TransferFailed();
    }

    /*//////////////////////////////////////////////////////////////
                            ERC20 OPERATIONS
    //////////////////////////////////////////////////////////////*/

    function safeTransferFrom(
        ERC20 token,
        address from,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x23b872dd00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), from) // Append the "from" argument.
            mstore(add(freeMemoryPointer, 36), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 68), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 100 because the length of our calldata totals up like so: 4 + 32 * 3.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 100, 0, 32)
            )
        }

        // require(success, "TRANSFER_FROM_FAILED");
        if (!success) revert TransferFailed();
    }

    function safeTransfer(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        assembly ("memory-safe") {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0xa9059cbb00000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), and(to, 0xffffffffffffffffffffffffffffffffffffffff)) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        // require(success, "TRANSFER_FAILED");
        if (!success) revert TransferFailed();
    }

    function safeApprove(
        ERC20 token,
        address to,
        uint256 amount
    ) internal {
        bool success;

        /// @solidity memory-safe-assembly
        assembly {
            // Get a pointer to some free memory.
            let freeMemoryPointer := mload(0x40)

            // Write the abi-encoded calldata into memory, beginning with the function selector.
            mstore(freeMemoryPointer, 0x095ea7b300000000000000000000000000000000000000000000000000000000)
            mstore(add(freeMemoryPointer, 4), to) // Append the "to" argument.
            mstore(add(freeMemoryPointer, 36), amount) // Append the "amount" argument.

            success := and(
                // Set success to whether the call reverted, if not we check it either
                // returned exactly 1 (can't just be non-zero data), or had no return data.
                or(and(eq(mload(0), 1), gt(returndatasize(), 31)), iszero(returndatasize())),
                // We use 68 because the length of our calldata totals up like so: 4 + 32 * 2.
                // We use 0 and 32 to copy up to 32 bytes of return data into the scratch space.
                // Counterintuitively, this call must be positioned second to the or() call in the
                // surrounding and() call or else returndatasize() will be zero during the computation.
                call(gas(), token, 0, freeMemoryPointer, 68, 0, 32)
            )
        }

        // require(success, "APPROVE_FAILED");
        if (!success) revert ApproveFailed();
    }
}

I got the same issue today. I was trying to use FXS(0x853d955aCEf822Db058eb8505911ED77F175b99e) for the AAVE flashloan contract! Thanks a lot !

@amalnathsathyan are you using the latest version of solmate?
This issue was fixed a couple of months ago in PR
#347