Insufficient Allowance Error

Hello, folks,

I'm a noob in Solidity, this is my very first attempt to create a more complicated scenario, but I'm stuck with this error when running my test case:

CryptoMarketplace
    ✔ should deploy contracts
    ✔ should mint payment tokens for buyer
    ✔ should mint seller tokens for seller (101ms)
    ✔ should allow seller to create a listing
    1) should allow buyer to purchase tokens


  4 passing (1s)
  1 failing

  1) CryptoMarketplace
       should allow buyer to purchase tokens:
     Error: VM Exception while processing transaction: reverted with reason string 'Insufficient allowance'
    at CryptoMarketplace.mintToken (contracts/Marketplace.sol:59)
    at CryptoMarketplace.purchaseToken (contracts/Marketplace.sol:85)
    at EdrProviderWrapper.request (node_modules/hardhat/src/internal/hardhat-network/provider/provider.ts:444:41)
    at HardhatEthersSigner.sendTransaction (node_modules/@nomicfoundation/hardhat-ethers/src/signers.ts:125:18)
    at send (node_modules/ethers/src.ts/contract/contract.ts:313:20)
    at Proxy.purchaseToken (node_modules/ethers/src.ts/contract/contract.ts:352:16)
    at Context.<anonymous> (test/marketplace-test.js:62:20)

This is my smart contract:

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

import "@openzeppelin/contracts/token/ERC20/ERC20.sol";
import "@openzeppelin/contracts/access/extensions/AccessControlEnumerable.sol";
import "@openzeppelin/contracts/utils/ReentrancyGuard.sol";

contract CryptoMarketplace is ReentrancyGuard, AccessControlEnumerable {
    // Role definitions
    bytes32 public constant ADMIN_ROLE = keccak256("ADMIN_ROLE");

    // Variables
    address public paymentToken; // Payment token used in the marketplace
    uint256 public platformFee; // Fee percentage (e.g., 2 for 2%)
    address public escrowWallet; // Escrow wallet for holding fees

    struct TokenMetadata {
        string website;
        string author;
        string[3] socialLinks;
    }

    mapping(address => TokenMetadata) public tokenMetadata;
    mapping(address => bool) public blacklistedTokens;

    // Events
    event TokenMinted(address indexed token, address indexed owner, uint256 supply);
    event ListingCreated(address indexed seller, address indexed token, uint256 price, uint256 quantity);
    event TokenPurchased(address indexed buyer, address indexed seller, address indexed token, uint256 quantity, uint256 totalPaid);
    event FeeUpdated(uint256 oldFee, uint256 newFee);
    event PaymentTokenUpdated(address oldToken, address newToken);
    event TokenBlacklisted(address indexed token, bool blacklisted);
    event AllowanceChecked(address indexed owner, address indexed spender, uint256 allowance);

    // Constructor
    constructor(address _paymentToken, uint256 _platformFee, address _escrowWallet) {
        require(_paymentToken != address(0), "Invalid payment token address");
        require(_escrowWallet != address(0), "Invalid escrow wallet address");
        require(_platformFee <= 100, "Fee cannot exceed 100%");

        paymentToken = _paymentToken;
        platformFee = _platformFee;
        escrowWallet = _escrowWallet;

        _grantRole(DEFAULT_ADMIN_ROLE, msg.sender);
        _grantRole(ADMIN_ROLE, msg.sender);
    }

    // Mint ERC-20 tokens
    function mintToken(
        string memory name,
        string memory symbol,
        uint256 supply,
        uint8 decimals,
        string memory website,
        string memory author,
        string[3] memory socialLinks
    ) external nonReentrant {
        require(supply > 0, "Supply must be greater than zero");

        // Mint the ERC-20 token
        ERC20Mintable newToken = new ERC20Mintable(name, symbol, supply, decimals, msg.sender);

        // Save metadata
        tokenMetadata[address(newToken)] = TokenMetadata(website, author, socialLinks);

        emit TokenMinted(address(newToken), msg.sender, supply);
    }

    // Create a token listing
    function createListing(
        address token,
        uint256 price,
        uint256 quantity
    ) external nonReentrant {
        require(!blacklistedTokens[token], "Token is blacklisted");
        require(ERC20(token).balanceOf(msg.sender) >= quantity, "Insufficient token balance");

        // Approval check omitted for simplicity but required in actual implementation

        emit ListingCreated(msg.sender, token, price, quantity);
    }

    // Purchase tokens from a listing
    function purchaseToken(
        address seller,
        address token,
        uint256 quantity
    ) external nonReentrant {
        require(!blacklistedTokens[token], "Token is blacklisted");

        uint256 price = ERC20(token).balanceOf(seller); // Placeholder for price storage logic
        uint256 totalPrice = price * quantity;

        uint256 fee = (totalPrice * platformFee) / 100;
        uint256 netAmount = totalPrice - fee;

        uint256 allowance = ERC20(paymentToken).allowance(msg.sender, address(this));
        require(allowance >= totalPrice, "Insufficient allowance");
        emit AllowanceChecked(msg.sender, address(this), allowance);

        //Transfer fee to admin wallet
        require(ERC20(paymentToken).transferFrom(msg.sender, getRoleMember(DEFAULT_ADMIN_ROLE, 0), fee), "Fee transfer failed");
 
        //Transfer remaining amount of payment to escrow wallet
        require(ERC20(paymentToken).transferFrom(msg.sender, escrowWallet, netAmount), "Payment transfer failed");

        //Transfer listed tokens from sellet to buyer
        require(ERC20(token).transferFrom(seller, msg.sender, quantity), "Token transfer failed");

        emit TokenPurchased(msg.sender, seller, token, quantity, totalPrice);
    }

    // Admin: Update platform fee
    function updatePlatformFee(uint256 newFee) external onlyRole(ADMIN_ROLE) {
        require(newFee <= 100, "Fee cannot exceed 100%");
        emit FeeUpdated(platformFee, newFee);
        platformFee = newFee;
    }

    // Admin: Update payment token
    function updatePaymentToken(address newToken) external onlyRole(ADMIN_ROLE) {
        require(newToken != address(0), "Invalid token address");
        emit PaymentTokenUpdated(paymentToken, newToken);
        paymentToken = newToken;
    }

    // Admin: Blacklist or whitelist tokens
    function blacklistToken(address token, bool blacklist) external onlyRole(ADMIN_ROLE) {
        blacklistedTokens[token] = blacklist;
        emit TokenBlacklisted(token, blacklist);
    }
}

// ERC-20 Mintable Token Contract
contract ERC20Mintable is ERC20 {
    uint8 private _decimals;

    constructor(
        string memory name,
        string memory symbol,
        uint256 initialSupply,
        uint8 decimals_,
        address owner
    ) ERC20(name, symbol) {
        _decimals = decimals_;
        _mint(owner, initialSupply * (10 ** uint256(decimals_)));
    }

    function decimals() public view override returns (uint8) {
        return _decimals;
    }
}

And this is my test:

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("CryptoMarketplace", function () {
    let deployer, escrow, seller, buyer;
    let paymentToken, sellerToken, marketplace;

    before(async () => {
        [deployer, escrow, seller, buyer] = await ethers.getSigners();

        // Deploy Payment Token
        const PaymentToken = await ethers.getContractFactory("MockPaymentToken");
        paymentToken = await PaymentToken.deploy();
        await paymentToken.waitForDeployment();

        // Deploy Seller Token
        const SellerToken = await ethers.getContractFactory("MockSellerToken");
        sellerToken = await SellerToken.deploy();
        await sellerToken.waitForDeployment();

        // Deploy Marketplace
        const Marketplace = await ethers.getContractFactory("CryptoMarketplace");
        marketplace = await Marketplace.deploy(paymentToken.target, 2, escrow.address);
        await marketplace.waitForDeployment();

        // Mint test tokens (seller token) for the seller
        await sellerToken.connect(deployer).transfer(seller.address, ethers.parseEther("100"));
    });

    it("should deploy contracts", async () => {
        expect(await paymentToken.target).to.properAddress;
        expect(await sellerToken.target).to.properAddress;
        expect(await marketplace.target).to.properAddress;
    });

    it("should mint payment tokens for buyer", async () => {
        await paymentToken.connect(deployer).transfer(buyer.address, ethers.parseEther("100"));
        const balance = await paymentToken.balanceOf(buyer.address);
        expect(balance.toString()).to.equal(ethers.parseEther("100").toString());
    });

    it("should mint seller tokens for seller", async () => {
        const balance = await sellerToken.balanceOf(seller.address);
        expect(balance.toString()).to.equal(ethers.parseEther("100").toString());
    });

    it("should allow seller to create a listing", async () => {
        await sellerToken.connect(seller).approve(marketplace.target, ethers.parseEther("50"));
        const tx = await marketplace.connect(seller).createListing(
            sellerToken.target,
            ethers.parseEther("1"), // Price per token
            ethers.parseEther("50") // Quantity
        );
        await tx.wait();
    });

    it("should allow buyer to purchase tokens", async () => {
        // Approve payment tokens for marketplace
        await paymentToken.connect(buyer).approve(marketplace.target, ethers.parseEther("50"));
    
        // Buyer purchases tokens
        const tx = await marketplace.connect(buyer).purchaseToken(
            seller.address,
            sellerToken.target,
            ethers.parseEther("50")
        );
        await tx.wait();
    
        // Validate buyer's balance
        const buyerBalance = await sellerToken.balanceOf(buyer.address);
        expect(buyerBalance.toString()).to.equal(ethers.parseEther("50").toString());
    });
});

I tried some different approaches, but none had an effect.

Please, can anyone pinpoint what I'm doing wrong and suggest possible solutions?

Thank you in advance.

Kindly.

Hi, welcome to the community! :wave:

I think there is something wrong as mentioned above line in the function purchaseToken, price is 100 * 10**18, quantity is 50 * 10 ** 18, so the result is double counting 10**18, this leads to your allowance is not enough. Maybe you can change the code to:
uint256 totalPrice = price * quantity / 1e18;

BTW, you can use the Hardhat solidity console.log to check the value in the contracts, it is convenient for debugging the contract code.

1 Like

if i make ask why should anyone mint token isn't it suppose to be the owner ( the one who deploy the contract that should mint the token), or is this how you want your smart contract to behave

you are missing the approve function of the erc20 token , you need to approve the amount before we can transferFrom

1 Like

the createListing parameter price is not used

1 Like