Issue giving in approval for the ERC20 token transfer to a secondary contract

274 views Asked by At

Here are the two codes for My ERC20 token, which allow customer to get tokens on every purchase and then avail discount with the number of tokens. Once you avail the tokens, you send the used tokens to an unspent address. I am getting stuck, where i need to transfer the tokens to an unspent address. It is showing me an error of 0 allowance, but i have mentioned the allowance as uint256 higher limit.

XYZtoken.sol

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract XYZtoken is ERC20 {

    address payable public owner;

    constructor(uint256 initialSupply) ERC20("XYZtoken", "XYZT") {
        owner = payable (msg.sender);
        _mint(owner, initialSupply);
    }

    function mintExtraTokens(uint256 amount) public onlyOwner {
        _mint(owner, amount);
    }

    function approveDiscountContract(address spender, uint256 amount) public{
        _approve(owner, spender, amount);
    }

    // function usedToken(address unspentaddress, uint256 amount) public {
    //     _burn(unspentaddress, amount);
    // }

    function balanceOf() view public {
        balanceOf(msg.sender);
    }

    modifier onlyOwner {
        require(msg.sender == owner, "Only owner can access this function");
        _;
    }
}

And DiscountContract.sol

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

import "./XYZtoken.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract DiscountContract {
    XYZtoken public token;
    address payable public companyAddress;
    address public unspentAddress;

    struct Customer {
        uint256 tokensEarned;
        // uint256 discountRate;
    }

    mapping(address => Customer) public customers;
    
    event TokensEarned(address indexed customer, uint256 amount);
    event DiscountAvailed(address indexed customer, uint256 discountRate);
    event TokensSentToCompanyAddress(address indexed customer, uint256 tokensSent);
    event BalanceLogged(address sender, uint256 amount);
    event excessPaidBack(address sender, uint256 amount);

    constructor(address _tokenAddress) {
        token = XYZtoken(_tokenAddress);
        companyAddress = payable(msg.sender);
        unspentAddress = 0x000000000000000000000000000000000000dEaD;
        token.approveDiscountContract(address(this), type(uint256).max);
    }   

    function availDiscount(address sender) public view returns (uint256) {
        if (token.balanceOf(sender) < 10) {
            return 0;
        }

        // Calculate discount rate based on earned tokens
        uint256 discount = token.balanceOf(sender) / 10;
        if (discount > 5) {
            discount = 5;
        }

        // Transfer the discount tokens from the customer to the unspent address
        return discount;
    }


    function purchaseProducts(uint256 amount) payable external {
        address payable sender = payable(msg.sender);
        // Validate purchase, e.g., product price, availability, etc.
        require(amount > 0, "Amount must be greater than 0");
        require(sender != companyAddress && sender != unspentAddress, "Cannot purchase product");
        //Avail the discount using the tokens
        
        uint256 discount = availDiscount(sender)*10;

        require(discount <=10, "The balance is less than discount");
        // token.approveDiscountContract(address(this), discount*2);
        token.approve(address(this), discount);
        token.transferFrom(sender, unspentAddress, discount);

        uint256 discountedAmount = ((100-discount)/100)*(amount); 
        //Check whether sufficient balance is present or not in the transaction
        require(sender.balance >= discountedAmount*(10**18), "Insufficient ETH balance in the contract");

        //Transfer ETH to the company for
        

        uint256 excessPayment = msg.value - discountedAmount * (10**18);

        // Refund the excess payment to the purchaser
        if (excessPayment > 0) {
            payable(sender).transfer(excessPayment);
            emit excessPaidBack(sender, excessPayment);
        }
        require(msg.value >= discountedAmount * (10**18), "Payment is less than the required amount");

        payable(companyAddress).transfer(discountedAmount*10**18);


        require(msg.value == discountedAmount * (10**18), "Amount sent must match the purchase amount");
        require(token.balanceOf(companyAddress) >= discountedAmount, "Insufficient tokens");
        uint256 earnedTokens = discountedAmount;

        // Mint tokens to the customer
        // token.transferFrom(companyAddress, sender, earnedTokens);
        // token.transferFrom(sender, earnedTokens);
        token.transferFrom(companyAddress, sender, earnedTokens);

        // Update customer's token balance
        customers[sender].tokensEarned += earnedTokens;
        emit TokensEarned(sender, earnedTokens);

        
    }
}

Here is the error:

transact to DiscountContract.purchaseProducts errored: Error occured: revert.

revert
    The transaction has been reverted to the initial state.
Error provided by the contract:
ERC20InsufficientAllowance
Parameters:
{
 "spender": {
  "value": "0xd8b934580fcE35a11B58C6D73aDeE468a2833fa8",
  "documentation": "Address that may be allowed to operate on tokens without being their owner."
 },
 "allowance": {
  "value": "0",
  "documentation": "Amount of tokens a `spender` is allowed to operate with."
 },
 "needed": {
  "value": "10",
  "documentation": "Minimum amount required to perform a transfer."
 }
}
Debug the transaction to get more information.

I am expecting that the logic works correctly. I have wasted 4+ hours debugging what went wrong, and with my very very limited solidity exposure, the lack of print() statements or console.log() statements is straight up headache for me

2

There are 2 answers

2
Mert Çevik On

It seems like the error is related to the allowance for transferring tokens. Specifically, the spender (in this case, your DiscountContract) is not allowed to operate on tokens owned by the sender.

In your DiscountContract constructor, you have this line:

Solution:

token.approveDiscountContract(address(this), type(uint256).max);

This line approves the DiscountContract to spend an unlimited amount of tokens on behalf of the owner. However, in the purchaseProducts function, u are trying to transfer tokens using:

Solution:

token.transferFrom(sender, unspentAddress, discount);

This line requires the sender to have approved the DiscountContract to spend at least discount amount of tokens.

If you fix this, U should add a line in your purchaseProducts function to ensure the sender has approved the DiscountContract to spend the required amount of tokens:

Solution:

token.approve(address(this), discount);

U should add this line before the token.transferFrom(sender, unspentAddress, discount); line in your purchaseProducts function.

Make sure to handle the case where the token.approve function fails. U can do this by checking the return value and reverting the transaction if it fails.

0
Akeel Ahmed Qureshi On

The issue might be connected to the approval of the spender's tokens for your discount contract. To address this, you can consider implementing a feature where a user, in this instance, your customer, authorizes your discount contract to utilize a specific quantity of tokens on their behalf. Once this authorization is in place, you can smoothly transfer your customer's tokens to the designated 'unspentAddress' destination.