Kamil
Kamil

Reputation: 73

How do you transfer an ERC-721 token using an impersonated address on an Ethereum mainnet fork?

I'm writing a contract that involves transferring an ERC-721 token from one user to another. In order to test that this works with existing NFT collections, I'm using ganache-cli to fork mainnet and impersonate the holder of the ERC-721 token in question. I've confirmed on Etherscan that the address I'm unlocking is indeed the holder of the ERC-721 token that I'm trying to transfer.

First, I'm forking mainnet using ganache-cli:

ganache-cli -f <INFURA_MAINNET_ENDPOINT> -d -i 66 1 --unlock <HOLDER_ADDRESS>

My smart contract code includes:

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

interface IERC721 {
    function ownerOf(uint256 _tokenId) external returns (address);
    function safeTransferFrom(address _from, address _to, uint256 _tokenId) external payable;
}
interface CryptopunkInterface {
    function transferPunk(address _to, uint _tokenId) external;
}

and contains this function:

    /// @dev Sells NFT into a bid (i.e., "hits" the bid)
    /// @param _bidderAddress Address of the bidder
    /// @param _nftAddress Address of collection to which the bid applies
    /// @param _tokenId Token id of the NFT in question
    /// @param _expectedWeiPriceEach Price (in wei) that seller expects to receive for each NFT
    /// @return Proceeds remitted to seller
    function hitBid(address _bidderAddress, address _nftAddress, uint256 _tokenId, uint256 _expectedWeiPriceEach) public returns (uint256) {
        console.log("msg.sender of hitBid: ", msg.sender);
        // Initialize bid
        Bid memory bid = bids[_bidderAddress][_nftAddress];
        // Require that bid exists
        require(bid.quantity > 0, "This bid does not exist.");
        // Require that bid amount is at least what the seller expects
        require(bid.weiPriceEach >= _expectedWeiPriceEach, "Bid is insufficient.");
        // Decrement bidder's bid quantity for this collection
        bids[_bidderAddress][_nftAddress].quantity = bid.quantity - 1;
        // Compute platform fee proceeds
        uint256 platformFeeProceeds = bid.weiPriceEach * platformFee / 10000;
        // Remit platform fee proceeds to owner
        sendValue(OWNER, platformFeeProceeds);
        // Transfer NFT to bidder
        // Check whether _nftAddress is Cryptopunks address
        if (_nftAddress == 0xb47e3cd837dDF8e4c57F05d70Ab865de6e193BBB) {
            CryptopunkInterface(_nftAddress).transferPunk(_bidderAddress, _tokenId);
        } else {
            console.log("ownerOf NFT being sold: ", IERC721(_nftAddress).ownerOf(_tokenId));
            IERC721(_nftAddress).safeTransferFrom(msg.sender, _bidderAddress, _tokenId);
        }
        // Compute seller proceeds
        uint256 sellerProceeds = bid.weiPriceEach - platformFeeProceeds;
        // Remit seller proceeds to seller
        sendValue(payable(msg.sender), sellerProceeds);
        // Emit new trade event
        emit NewTrade(_bidderAddress, msg.sender, _nftAddress, bid.weiPriceEach, 1, _tokenId);
        // Return seller proceeds
        return sellerProceeds;
    }

When I run truffle test, executing the function on behalf of the unlocked holder address, I get this error:

Error: Returned error: VM Exception while processing transaction: revert ERC721: transfer caller is not owner nor approved -- Reason given: ERC721: transfer caller is not owner nor approved.

UPDATE:

I switched from using ganache-cli to using Hardhat to fork mainnet. I'm impersonating the relevant addresses in my test.js file:

const BAYC_HOLDER_ADDRESS = "0x54BE3a794282C030b15E43aE2bB182E14c409C5e";

await hre.network.provider.request({
  method: "hardhat_impersonateAccount",
  params: [BAYC_HOLDER_ADDRESS],
});

I've also verified that msg.sender of hitBid is indeed the ownerOf the NFT in question with the console.log statements above.

msg.sender of hitBid:  0x54be3a794282c030b15e43ae2bb182e14c409c5e
ownerOf NFT being sold:  0x54be3a794282c030b15e43ae2bb182e14c409c5e

Nonetheless, I'm still getting the same error:

Error: VM Exception while processing transaction: reverted with reason string 'ERC721: transfer caller is not owner nor approved'

Upvotes: 2

Views: 922

Answers (1)

Kamil
Kamil

Reputation: 73

The reason you are getting this error is because msg.sender of hitBid() is not the same as msg.sender of IERC721(_nftAddress).safeTransferFrom().

The seller needs to sign two transactions:

  1. The user needs to sign approve(yourContractAddress, tokenId)
  2. The user needs to sign hitBid()

That will prevent hitBid() from reverting, since your contract address (msg.sender of safeTransferFrom()) will then be approved to make the transfer.

Upvotes: 1

Related Questions