I'm having a very interesting problem, I have two contracts:
- An Openzeppelin ERC721 token with a mint function that utilizes
_safeMint
- An upgradable marketplace contract (
IERC721HolderUpgradeable
) that usesAddressUpgradeable
(callFunctionWithValue
) to call themint
function in #1
If I call the function in #2 that uses callFunctionWithValue
to call the mint
function in #1, I can trace the transaction to failing at _checkOnERC721Received
when it calls IERC721Receiver(to).onERC721Received(_msgSender(), from, tokenId, _data)
at line 395. Then, the tx fails and the token provides no revert message.
If I add a method to #2 and cast the token address like so: ERC721(token_address).mint(...)
the transaction succeeds with no issue. This seems like a very niche error in the EVM/compiler, but I am not so sure.
I tried this in solc 0.8.9, 0.8.10 and 0.8.11 with the same results.
Market.sol
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";
import "@openzeppelin/contracts-upgradeable/access/OwnableUpgradeable.sol";
import "@openzeppelin/contracts/interfaces/IERC20.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC1155/IERC1155ReceiverUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/token/ERC721/IERC721ReceiverUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/introspection/IERC165Upgradeable.sol";
import "@openzeppelin/contracts-upgradeable/utils/AddressUpgradeable.sol";
contract RoolahShopUpgradableV3 is
IERC165Upgradeable,
IERC721ReceiverUpgradeable,
IERC1155ReceiverUpgradeable,
Initializable,
OwnableUpgradeable
{
using AddressUpgradeable for address;
/**
* Forwards a call to a specified contract address, targeting the given function
* with the provided abi encoded parameters.
*
* This will allow the market to mint directly from projects, etc. (essentially, allows the contract to behave like a wallet)
*
* @param contractAt - the address of the target contract
* @param encodedCall - the ABI encoded function call (use web3.eth.abi.encodeFunctionCall)
*/
function externalCall(address contractAt, bytes calldata encodedCall)
external
payable
onlyOwner
returns (bytes memory)
{
return contractAt.functionCallWithValue(encodedCall, msg.value);
}
function supportsInterface(bytes4 interfaceId)
public
view
virtual
returns (bool)
{
return
interfaceId == type(IERC1155ReceiverUpgradeable).interfaceId ||
interfaceId == type(IERC721ReceiverUpgradeable).interfaceId;
}
function onERC1155Received(
address,
address,
uint256,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155Received.selector;
}
function onERC1155BatchReceived(
address,
address,
uint256[] memory,
uint256[] memory,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC1155BatchReceived.selector;
}
function onERC721Received(
address,
address,
uint256,
bytes memory
) public virtual override returns (bytes4) {
return this.onERC721Received.selector;
}
}
Token.sol
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/extensions/ERC721Enumerable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";
import "@openzeppelin/contracts/utils/Strings.sol";
contract MockERC721 is ERC721Enumerable, Ownable {
/**
* Public so that we can test
*/
function safeMint(address user, uint256[] calldata ids) public {
uint256 i;
for (i; i < ids.length; i++) {
_safeMint(user, ids[i]);
}
}
}