ERC20 Tokens – How Do I Set the Allowance for ERC20 Tokens in Foundry?

foundrysoliditytesting

In the following minimum viable test I'm failing to mock the allowance so that the ReceiverContract can transfer ERC20 tokens. The expected use-case would be a user (=externally-owned account) interacts with the ReceiverContract – if conditions are passed the contract transfers ERC20 tokens from the user wallet to the contract itself.

In this example with DAI ERC20 tokens the test fails with Dai/insufficient-allowance (using the mainnet fork mechanism of Forge).

// SPDX-License-Identifier: MIT
pragma solidity ^0.8.13;

import "forge-std/Test.sol";
import "forge-std/console.sol";
import "openzeppelin-contracts/contracts/token/ERC20/IERC20.sol";

    using stdStorage for StdStorage;

contract ReceiverContract {
    address public contractAddress;

    constructor() {
        contractAddress = address(this);
    }

    function sendErc20ToContract(address _tokenAddress, uint256 amount) public {
        IERC20 token = IERC20(_tokenAddress);
        require(token.balanceOf(msg.sender) >= amount);
        require(token.allowance(msg.sender, contractAddress) >= amount);

        token.transferFrom(msg.sender, contractAddress, amount);
    }
}

contract TestReceiverContract is Test {
    ReceiverContract testReceiver;

    function setUp() public {
        testReceiver = new ReceiverContract();
    }

    function addErc20TokenBalance(address who, address token, uint256 amount) internal {
        deal(address(token), who, amount);

        vm.prank(msg.sender);
        IERC20(token).approve(testReceiver.contractAddress(), amount);
        uint256 allowanceAmount = IERC20(token).allowance(msg.sender, testReceiver.contractAddress());
        emit log_named_uint("allowance amount from external ERC20", allowanceAmount);
    }

    function testTransfer() public {
        address daiTokenAddress = 0x6B175474E89094C44Da98b954EedeAC495271d0F;
        uint16 amount = 2342;

        addErc20TokenBalance(msg.sender, daiTokenAddress, amount);

        vm.prank(msg.sender);
        testReceiver.sendErc20ToContract(daiTokenAddress, amount);
    }
}

What I've tried:

  • setting the value in the ERC20 allowance mapping "manually" using stdstore.target(token).sig(IERC20(token).allowance.selector -> didn't work
  • changing the allowance to different addresses, changing msg.sender in all the places, etc

Is there a bug in my test or is the code conceptually correct? Any hints highly appreciated!

Best Answer

Hey are you still trying to find a solution to this issue?

IERC20(token).approve(testReceiver.contractAddress(), amount);
uint256 allowanceAmount = IERC20(token).allowance(msg.sender, 
testReceiver.contractAddress());

vs

IERC20(token).approve(address(testReceiver), amount);
uint256 allowanceAmount = IERC20(token).allowance(msg.sender, 
address(testReceiver));

Seems that calling into ReceiverContract using testReceiver.contractAddress() was the point of failure. Casting the contract pointer as an address returns a valid allowance amount!

Thanks for being my first EthStackExchange issue I've fixed lol! Just started learning Foundry a week or so ago, awfully pleased with it as a testing suite I must say!

Related Topic