0

Building an Auction for ERC20 Tokens. Scripts are compiling. 2/3 tests pass. [FAIL. Reason: EvmError: Revert] for 1/3. It may be an issue with the OpenZeppelin import, it may be out of date, wrong import path, or something else. I am unable to resolve.

Auction.sol

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

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

contract Auction {
    address public owner;
    IERC20 public token;
    uint256 public auctionEndTime;
    uint256 public totalTokens;
    bool public auctionEnded;

    mapping(address => uint256) public bids;
    mapping(address => bool) public bidders; // Track bidders
    address[] public bidderList; // List of bidders

    event AuctionStarted(uint256 duration, uint256 totalTokens);
    event BidPlaced(address indexed bidder, uint256 amount);
    event AuctionEnded(address indexed winner, uint256 amount);

    modifier onlyOwner() {
        require(msg.sender == owner, "Not the contract owner");
        _;
    }

    modifier auctionActive() {
        require(block.timestamp < auctionEndTime, "Auction has ended");
        require(!auctionEnded, "Auction already ended");
        _;
    }

    modifier auctionEndedOnly() {
        require(
            block.timestamp >= auctionEndTime || auctionEnded,
            "Auction still active"
        );
        _;
    }

    constructor() {
        owner = msg.sender; // Set contract deployer as owner
    }

    function startAuction(
        address _token,
        uint256 _duration,
        uint256 _totalTokens
    ) external onlyOwner {
        require(
            auctionEndTime == 0 || block.timestamp >= auctionEndTime,
            "Previous auction not ended"
        );
        token = IERC20(_token);
        require(
            token.balanceOf(address(this)) >= _totalTokens,
            "Not enough tokens for auction"
        );
        auctionEndTime = block.timestamp + _duration;
        totalTokens = _totalTokens;
        auctionEnded = false;
        emit AuctionStarted(_duration, _totalTokens);
    }

    function placeBid() external payable auctionActive {
        require(msg.sender != owner, "Owner cannot place bids");
        require(msg.value > 0, "Bid must be greater than 0");

        bids[msg.sender] += msg.value;
        bidders[msg.sender] = true; // Register the bidder
        bool isNewBidder = true;
        for (uint256 i = 0; i < bidderList.length; i++) {
            if (bidderList[i] == msg.sender) {
                isNewBidder = false;
                break;
            }
        }
        if (isNewBidder) {
            bidderList.push(msg.sender);
        }
        emit BidPlaced(msg.sender, msg.value);
    }

    function endAuction() external onlyOwner auctionEndedOnly {
        require(!auctionEnded, "Auction already ended");
        console.log("Auction not ended, proceeding");

        auctionEnded = true;
        uint256 totalBidAmount = address(this).balance;
        require(totalBidAmount > 0, "No bids placed");
        console.log("Total bid amount:", totalBidAmount);

        uint256 tokenDistributed = 0;
        console.log("Number of bidders:", bidderList.length);
        for (uint256 i = 0; i < bidderList.length; i++) {
            address bidder = bidderList[i];
            uint256 tokenAmount = (bids[bidder] * totalTokens) / totalBidAmount;
            console.log("Bidder", i, ":", bidder);
            console.log("Bid amount:", bids[bidder]);
            console.log("Token amount to distribute:", tokenAmount);

            if (tokenAmount > 0) {
                require(
                    token.transfer(bidder, tokenAmount),
                    "Token transfer failed"
                );
                console.log("Tokens transferred to bidder");
                emit AuctionEnded(bidder, tokenAmount);
                tokenDistributed += tokenAmount;
            }
            bids[bidder] = 0; // Clear the bid amount
        }

        require(
            tokenDistributed <= totalTokens,
            "Distributed more tokens than available"
        );
        console.log("Total tokens distributed:", tokenDistributed);

        uint256 etherToTransfer = address(this).balance;
        console.log("Ether to transfer to owner:", etherToTransfer);
        payable(owner).transfer(etherToTransfer);
        console.log("Ether transferred to owner");
    }

    function withdrawRemainingTokens() external onlyOwner auctionEndedOnly {
        uint256 remainingTokens = token.balanceOf(address(this));
        require(
            token.transfer(owner, remainingTokens),
            "Token transfer failed"
        );
    }
}

Auction.t.sol

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

import "forge-std/Test.sol";
import "forge-std/console.sol";
import "../src/Auction.sol";
import "@openzeppelin/contracts/token/ERC20/ERC20.sol";

contract MockERC20 is ERC20 {
    constructor() ERC20("Mock Token", "MKT") {
        _mint(msg.sender, 1000 * 10 ** 18);
    }
}

contract AuctionTest is Test {
    Auction public auction;
    MockERC20 public token;
    address public owner;
    address public bidder1;
    address public bidder2;

    function setUp() public {
        owner = address(this); // Test contract is the owner
        bidder1 = address(0x1);
        bidder2 = address(0x2);

        auction = new Auction();
        token = new MockERC20();

        // Transfer tokens to the auction contract
        require(
            token.transfer(address(auction), 100 * 10 ** 18),
            "Token transfer to auction failed"
        );

        // Approve the auction contract to spend tokens
        require(
            token.approve(address(auction), type(uint256).max),
            "Token approval failed"
        );

        // Verify the transfer and approval
        assertEq(
            token.balanceOf(address(auction)),
            100 * 10 ** 18,
            "Auction didn't receive tokens"
        );
        assertEq(
            token.allowance(address(this), address(auction)),
            type(uint256).max,
            "Approval failed"
        );
    }

    function testStartAuction() public {
        vm.prank(owner);
        auction.startAuction(address(token), 1000, 100 * 10 ** 18);

        assertEq(
            auction.totalTokens(),
            100 * 10 ** 18,
            "Total tokens mismatch"
        );
        assertEq(
            address(auction.token()),
            address(token),
            "Token address mismatch"
        );
    }

    function testPlaceBid() public {
        vm.prank(owner);
        auction.startAuction(address(token), 1000, 100 * 10 ** 18);

        vm.prank(bidder1);
        vm.deal(bidder1, 1 ether); // Ensure bidder1 has enough ETH
        auction.placeBid{value: 1 ether}();

        assertEq(auction.bids(bidder1), 1 ether, "Bid amount mismatch");
    }

    function testEndAuction() public {
        console.log("Starting testEndAuction");
        vm.prank(owner);
        auction.startAuction(address(token), 1000, 100 * 10 ** 18);
        console.log("Auction started");

        vm.prank(bidder1);
        vm.deal(bidder1, 2 ether);
        auction.placeBid{value: 2 ether}();
        console.log("Bidder1 placed bid");

        vm.prank(bidder2);
        vm.deal(bidder2, 3 ether);
        auction.placeBid{value: 3 ether}();
        console.log("Bidder2 placed bid");

        vm.warp(block.timestamp + 1001);
        console.log("Time warped to end of auction");

        uint256 initialOwnerBalance = address(owner).balance;
        uint256 initialAuctionBalance = address(auction).balance;
        uint256 initialTokenBalance = token.balanceOf(address(auction));

        console.log("Initial owner ETH balance:", initialOwnerBalance);
        console.log("Initial auction ETH balance:", initialAuctionBalance);
        console.log("Initial auction token balance:", initialTokenBalance);

        vm.prank(owner);
        auction.endAuction();
        console.log("Auction ended");

        uint256 bidder1TokenBalance = token.balanceOf(bidder1);
        uint256 bidder2TokenBalance = token.balanceOf(bidder2);
        uint256 finalOwnerBalance = address(owner).balance;
        uint256 finalAuctionBalance = address(auction).balance;
        uint256 finalTokenBalance = token.balanceOf(address(auction));

        console.log("Bidder1 token balance:", bidder1TokenBalance);
        console.log("Bidder2 token balance:", bidder2TokenBalance);
        console.log("Final owner ETH balance:", finalOwnerBalance);
        console.log("Final auction ETH balance:", finalAuctionBalance);
        console.log("Final auction token balance:", finalTokenBalance);

        assertEq(
            bidder1TokenBalance,
            40 * 10 ** 18,
            "Bidder1 token balance mismatch"
        );
        assertEq(
            bidder2TokenBalance,
            60 * 10 ** 18,
            "Bidder2 token balance mismatch"
        );
        assertEq(
            finalOwnerBalance,
            initialOwnerBalance + 5 ether,
            "Owner didn't receive ETH"
        );
        assertEq(
            finalAuctionBalance,
            0,
            "Auction contract should have 0 ETH balance"
        );
        assertEq(
            finalTokenBalance,
            0,
            "Auction contract should have 0 token balance"
        );
    }
}

Output

Ran 1 test suite in 814.65ms (66.59ms CPU time): 2 tests passed, 1 failed, 0 skipped (3 total tests)

Failing tests: Encountered 1 failing test in test/Auction.t.sol:AuctionTest [FAIL. Reason: EvmError: Revert] testEndAuction() (gas: 380426)

Please advise.

Update:

Trace:

├─ [0] console::log("Ether to transfer to owner:", 5000000000000000000 [5e18]) [staticcall] │ └─ ← [Stop] ├─ [45] AuctionTest::fallback{value: 5000000000000000000}() │ └─ ← [Revert] EvmError: Revert └─ ← [Revert] EvmError: Revert

2
  • can you share the stack trace
    – Adam Boudj
    Commented Aug 14 at 15:41
  • @AdamBoudj ├─ [0] console::log("Ether to transfer to owner:", 5000000000000000000 [5e18]) [staticcall] │ └─ ← [Stop] ├─ [45] AuctionTest::fallback{value: 5000000000000000000}() │ └─ ← [Revert] EvmError: Revert └─ ← [Revert] EvmError: Revert Commented Aug 14 at 19:45

1 Answer 1

1

I think you need to check the remapping or OpenZeppelin. If you've already done that, just import it like this

import "lib/openzeppelin-contracts/contracts/interfaces/IERC20.sol";

I ran the script, and while I didn't get an EVM error, there was an assertion failed error

1
  • Thanks, it was an issue with the remapping and there was also a dependency conflict. I reinstalled node/npm and imported openzeppelin-contracts IERC20. Commented Aug 15 at 12:41

Your Answer

By clicking “Post Your Answer”, you agree to our terms of service and acknowledge you have read our privacy policy.

Not the answer you're looking for? Browse other questions tagged or ask your own question.