soliditysmartcontractserc20

Allowance is returned zero even after approval


I was unit testing my contract and within the tests i approved that the smart contract should be able to spend the tokens of the msg.sender (the owner) and then tried to transferFrom function to transfer the tokens from the msg.sender to the contract. However, the first obstacle i faced was that the allowance is returned as zero when i called the transferFrom function. Now i was able to relearn that the transferFrom function should be another transaction for it to be able to work and i console loged all the variables making sure that the allowance is being changed when calling the approve function. However when i recall the transferFrom the error persists and shows me Insufficient balance!

` This is my deposit function where the transferFrom is called only if the approval was a success

/**
     * @notice addEth is a user function where the user chooses the pool and sends eth to it, funding it
     * @param depositAmount is the eth being sent from the user to the pool
     * @param poolId_ is the address of the pool being chosen by the user
     */
    function deposit(uint256 depositAmount, uint256 poolId_) public{
        
        // Check if the pool is either closed or paused or exists
        Pool storage p = checkIfPoolExistsOrClosed(poolId_);

        // Make sure the eth being sent is not equal to zero
        if (depositAmount <= 0) revert WrongAmount();

        // Check if the pool is empty, if it is the price of token is 1
        if(p.valueInPool == 0) {
            tokensForUser = depositAmount;
        }else {
            // Get the amount of tokens to be minted to the user
            tokensForUser = (depositAmount /
                (p.valueInPool/
                IProperSubsetERC20(p.poolTokenAddress).totalSupply()));
        }
        
        // check if the approval was a success
        if(!contractIsApproved[msg.sender]) revert ApprovalFailed();

        // Send the USDC tokens to the fund contract
        bool transfer  = IProperSubsetUSDC(usdcAddress).transferFrom(msg.sender, address(this), depositAmount);

        // Send the USDC tokens to the fund contract
        // (bool success,)=usdcAddress.delegatecall(abi.encodeWithSignature('transfer(address,uint256)', address(this), depositAmount));

        // Call the ERC20 contract to mint tokens to user
        IProperSubsetERC20(p.poolTokenAddress).mint(msg.sender, tokensForUser);

        // Update the amount of liquidity in the pool
        p.valueInPool = p.valueInPool + depositAmount;

        // Emit event after adding eth to pool
        emit Deposit(msg.sender, poolId_, depositAmount);
    }

This is my approval function where i call the approve function to add an allowance

    /**
     * @notice function to approve contract to spend the user's USDC tokens
     * @param amount of usdt willing to give the contract approval for spending
     */
    function approveUser(uint256 amount) public returns(bool){

        // Approve spending the msg.sender tokens
        (bool success,) =    usdcAddress.delegatecall(abi.encodeWithSignature('approve(address,uint256)', address(this), amount));

        // If the approve function is succesfull we update the map to show that this address is approved
        if(success){
            contractIsApproved[msg.sender] = true;
        }
        // Return if the function is successfull
        return success;
    }

`

Now these are the tests where the approve and the transferFrom function are called

it("Try approving for user 1 the transfer of their tokens from the contract", async function() {
      await deployFunds.connect(signers[1]).approveUser(1000);
    })

    it("Try depositing and the price of the pool tokens should be equal to one since it is new", async function() {
      const tx = await deployFunds.connect(signers[1]).deposit(1000, 2);
      const receipt = await tx.wait();
      
      filter = receipt.events?.filter((x) => {return x.event == "Deposit"});
      poolId = filter.length > 0 ? filter[0].args[1] : '0x000';
      tokensForUser = filter.length > 0 ? filter[0].args[2]: "0";
      mintedTokens = await deployERC20.balanceOf(user1);
      expect(filter.length).above(0);
      expect(poolId).to.equal(2);
      expect(tokensForUser).to.equal(1000);
    })

I tried console logging all the variables being changed in the approve function and everything was checking out but when i console log the allowance when calling the transferFrom is is printed as zero. I then tried to put the approve function and the transferFrom in each transaction alone and still the error persists as Insufficient allowance


Solution

  • (bool success,) =    usdcAddress.delegatecall(abi.encodeWithSignature('approve(address,uint256)', address(this), amount));
    

    Delegatecall stores the state changes (updates a storage slot) in your contract - not in the usdcAddress contract.

    In order to get approval from the user, they need to send a separate transaction executing the approve() function directly on the usdcAddress contract - not through any other contract.