ERC721A编译报错Invalid contract specified in override list
问题现象
部署开发完成的ERC721A智能合约时触发编译失败,合约代码如下:
// SPDX-License-Identifier: MIT pragma solidity ^0.8.4; import '@openzeppelin/contracts/access/Ownable.sol'; import '@openzeppelin/contracts/utils/Strings.sol'; import 'erc721a/contracts/IERC721A.sol'; import 'erc721a/contracts/extensions/erc721aQueryable.sol'; contract unNamed is ERC721AQueryable, Ownable { using Strings for uint256; uint256 public constant TOTAL_MAX_SUPPLY = 10000; uint256 public totalFreeMints = 1000; uint256 public teamAmount = 777; uint256 public maxFreeMintPerWallet = 2; uint256 public maxPublicMintPerWallet = 10; uint256 public publicTokenPrice = .0069 ether; string _contractURI; bool public saleStarted = false; uint256 public freeMintCount; mapping(address => uint256) public freeMintClaimed; string private _baseTokenURI; constructor() ERC721A('unNamed', 'unn') {} modifier callerIsUser() { require(tx.origin == msg.sender, 'unNamed: The caller is another contract'); _; } modifier underMaxSupply(uint256 _quantity) { require( _totalMinted() + _quantity <= TOTAL_MAX_SUPPLY - teamAmount, 'unNamed: Purchase exceeds max supply' ); _; } function mint(uint256 _quantity) external payable callerIsUser underMaxSupply(_quantity) { require(balanceOf(msg.sender) < maxPublicMintPerWallet, "unNamed: Mint limit exceeded"); require(saleStarted, 'unNamed: Sale has not begun '); if (_totalMinted() < (TOTAL_MAX_SUPPLY - teamAmount)) { if (freeMintCount >= totalFreeMints) { require(msg.value >= _quantity * publicTokenPrice, 'unNamed: Send more ETH!'); _mint(msg.sender, _quantity); } else if (freeMintClaimed[msg.sender] < maxFreeMintPerWallet) { uint256 _mintableFreeQuantity = maxFreeMintPerWallet - freeMintClaimed[msg.sender]; if (_quantity <= _mintableFreeQuantity) { freeMintCount += _quantity; freeMintClaimed[msg.sender] += _quantity; } else { freeMintCount += _mintableFreeQuantity; freeMintClaimed[msg.sender] += _mintableFreeQuantity; require( msg.value >= (_quantity - _mintableFreeQuantity) * publicTokenPrice, 'unNamed: Find more ETH to send' ); } _mint(msg.sender, _quantity); } else { require(msg.value >= (_quantity * publicTokenPrice), 'unNamed: Find more ETH to send'); _mint(msg.sender, _quantity); } } } function _baseURI() internal view virtual override returns (string memory) { return _baseTokenURI; } function tokenURI(uint256 tokenId) public view virtual override(ERC721A, IERC721A) returns (string memory) { if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); string memory baseURI = _baseURI(); return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ''; } function numberMinted(address owner) public view returns (uint256) { return _numberMinted(owner); } function _startTokenId() internal view virtual override returns (uint256) { return 1; } function ownerMint(uint256 _numberToMint) external onlyOwner underMaxSupply(_numberToMint) { _mint(msg.sender, _numberToMint); } function ownerMintToAddress(address _recipient, uint256 _numberToMint) external onlyOwner underMaxSupply(_numberToMint) { _mint(_recipient, _numberToMint); } function setFreeMintCount(uint256 _count) external onlyOwner { totalFreeMints = _count; } function setTeamAmount(uint256 _count) external onlyOwner { teamAmount = _count; } function setMaxFreeMintPerWallet(uint256 _count) external onlyOwner { maxFreeMintPerWallet = _count; } function setMaxPublicMintPerWallet(uint256 _count) external onlyOwner { maxPublicMintPerWallet = _count; } function setBaseURI(string calldata baseURI) external onlyOwner { _baseTokenURI = baseURI; } function contractURI() public view returns (string memory) { return _contractURI; } function setContractURI(string memory _URI) external onlyOwner { _contractURI = _URI; } function withdrawFunds() external onlyOwner { (bool success, ) = msg.sender.call{ value: address(this).balance }(''); require(success, 'unNamed: Transfer failed.'); } function withdrawFundsToAddress(address _address, uint256 amount) external onlyOwner { (bool success, ) = _address.call{ value: amount }(''); require(success, 'unNamed: Transfer failed.'); } function flipSaleStarted() external onlyOwner { saleStarted = !saleStarted; } }
编译报错信息
TypeError: Invalid contract specified in override list: "IERC721A".
--> contracts/unNamed.sol:75:58:
| 75 | function tokenURI(uint256 tokenId) public view virtual override(ERC721A, IERC721A) returns (string memory) {
| ^^^^^^^^^^^^^^^^^^^^^^^^^^^
Note: This contract:
--> erc721a/contracts/IERC721A.sol:10:1:
| 10 | interface IERC721A {
| ^ (Relevant source part starts here and spans across multiple lines).Error HH600: Compilation failed
错误原因
该报错和IERC721A的文件存放路径、导入语句是否正确没有关系,核心违反了Solidity的override语法规则:
只有当前合约的直接父合约/接口,才能被写入override列表。
当前合约直接继承的是ERC721AQueryable和Ownable,IERC721A是被ERC721A、ERC721AQueryable逐级继承的间接父接口,不属于直接父级,因此不能出现在override列表中。
另外代码中还存在一个隐藏问题:导入ERC721AQueryable的路径文件名大小写错误,官方库中该文件名为ERC721AQueryable.sol(首字母大写),当前写的是全小写开头的erc721aQueryable.sol,在Linux等大小写敏感的文件系统中会直接导致导入失败。
修复步骤
- 修正导入路径的大小写错误,将
import 'erc721a/contracts/extensions/erc721aQueryable.sol';修改为import 'erc721a/contracts/extensions/ERC721AQueryable.sol'; - 修改
tokenURI函数的override列表,移除间接父接口IERC721A,替换为直接父合约ERC721AQueryable,修正后的函数代码如下:
function tokenURI(uint256 tokenId) public view virtual override(ERC721AQueryable, ERC721A) returns (string memory) { if (!_exists(tokenId)) revert URIQueryForNonexistentToken(); string memory baseURI = _baseURI(); return bytes(baseURI).length != 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : ''; }
- 重新执行编译命令,上述错误会直接消除。
- 额外注意:当前mint函数的钱包铸造数量校验存在逻辑漏洞,
require(balanceOf(msg.sender) < maxPublicMintPerWallet)只校验了铸造前的持仓,没有校验本次铸造数量+持仓是否超过上限,用户可以一次铸造超过maxPublicMintPerWallet数量的NFT,建议同步修复该逻辑。
内容的提问来源于stack exchange,提问作者kjp

