Reputation: 73
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
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:
That will prevent hitBid() from reverting, since your contract address (msg.sender of safeTransferFrom()) will then be approved to make the transfer.
Upvotes: 1