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.