You need to enable JavaScript to run this app.
优惠活动
大模型
产品
解决方案
定价
更多

NFT合约转为可升级版本时出现Identifier编译错误求助

解决可升级NFT合约编译时的DeclarationError错误

编译报错的核心原因是未遵循OpenZeppelin可升级合约的完整规范,具体问题如下:

  • 未导入Initializable合约,导致initializer修饰符无法被识别
  • 使用了普通版本的OpenZeppelin合约(如ERC721URIStorage、AccessControl),而非专为可升级场景设计的Upgradeable版本——普通合约依赖构造函数初始化状态,可升级合约则要求通过initialize函数调用父合约的初始化逻辑
  • 原代码未调用父合约的初始化方法,导致父合约状态无法正确初始化

修复步骤

  1. 将所有普通合约的导入替换为对应的Upgradeable版本,路径调整为@openzeppelin/contracts-upgradeable/开头
  2. 让合约继承Initializable,这是可升级合约的基础
  3. 在initialize函数中调用父合约的初始化方法,比如__ERC721_init、__Pausable_init、__AccessControl_init
  4. 确保所有使用的修饰符、函数都来自升级版本的合约

修改后的完整合约代码

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

import "@openzeppelin/contracts-upgradeable/token/ERC721/extensions/ERC721URIStorageUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/security/PausableUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/access/AccessControlUpgradeable.sol";
import "@openzeppelin/contracts-upgradeable/proxy/utils/Initializable.sol";

contract ERC721CarbonAsset is Initializable, ERC721URIStorageUpgradeable, PausableUpgradeable, AccessControlUpgradeable {
  
  bytes32 public constant PAUSER_ROLE = keccak256("PAUSER_ROLE");
  bytes32 public constant MINTER_ROLE = keccak256("MINTER_ROLE");
  bytes32 public constant BURNER_ROLE = keccak256("BURNER_ROLE");
  bytes32 public constant OPERATOR_ROLE = keccak256("OPERATOR_ROLE");
  
  // Base URI
  string private _baseUri;
  address _forwarder;
  
  mapping(uint256 => string) private _digests;
  mapping(uint256 => string) private _infoRoots;
  
  // Addresses under operator control
  mapping(address => bool) private _operatorEnabled;
  
  function initialize() initializer public {
    __ERC721_init("", "");
    __Pausable_init();
    __AccessControl_init();
    
    _baseUri = "";
    _setupRole(DEFAULT_ADMIN_ROLE, msg.sender);
    _setupRole(PAUSER_ROLE, msg.sender);
    _setupRole(MINTER_ROLE, msg.sender);
    _setupRole(BURNER_ROLE, msg.sender);
    _setupRole(OPERATOR_ROLE, msg.sender);
  }
  
  function pause() external onlyRole(PAUSER_ROLE) {
    _pause();
  }

  function unpause() external onlyRole(PAUSER_ROLE) {
    _unpause();
  }

  function _beforeTokenTransfer(address from, address to, uint256 tokenId, uint256 batchSize) internal virtual override(ERC721Upgradeable) {
    super._beforeTokenTransfer(from, to, tokenId, batchSize);
    require(!paused(), "ERC721Pausable: token transfer while paused");
  }
  
  function mint(address to, uint256 tokenId, string memory tokenUri, string memory digest) public onlyRole(MINTER_ROLE) {
    _mint(to, tokenId);
    _setTokenURI(tokenId, tokenUri);
    _digests[tokenId] = digest;
  }

  function burn(uint256 tokenId) public onlyRole(BURNER_ROLE) {
    _burn(tokenId);
  }
  
  function setBaseURI(string memory uri) external onlyRole(OPERATOR_ROLE) {
    _baseUri = uri;
  }

  function _baseURI() internal view virtual override returns (string memory) {
    return _baseUri;
  }

  function infoRoot(uint256 tokenId) external view virtual returns (string memory) {
    require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");

    string memory _infoRoot = _infoRoots[tokenId];

    if (bytes(_infoRoot).length == 0) {
      return "";
    }

    return _infoRoot;
  }

  function setInfoRoot(uint256 tokenId, string memory _infoRoot) external onlyRole(OPERATOR_ROLE) whenNotPaused() {
    require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
    _infoRoots[tokenId] = _infoRoot;
  }
  
  function digest(uint256 tokenId) external view virtual returns (string memory) {
    require(_exists(tokenId), "ERC721URIStorage: URI query for nonexistent token");

    string memory digest = _digests[tokenId];

    if (bytes(digest).length == 0) {
      return "";
    }

    return digest;
  }
  
  function setDigest(uint256 tokenId, string memory digest) external onlyRole(OPERATOR_ROLE) whenNotPaused() {
    require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
    _digests[tokenId] = digest;
  }

  function operatorTransfer(address recipient, uint256 tokenId) external onlyRole(OPERATOR_ROLE) whenNotPaused() returns (bool) {
    address owner = ownerOf(tokenId);
    require(isOperatorControlled(owner), "ERC721: sender not under operator control");
    _approve(msg.sender, tokenId);
    transferFrom(owner, recipient, tokenId);
    return true;
  }
  
  function enableOperatorControl() external whenNotPaused() returns (bool) {
    require(msgSender() != address(0), "ERC20: owner is a zero address");
    require(!isOperatorControlled(msgSender()), "ERC20: owner already under operator control");
    _operatorEnabled[msgSender()] = true;
    return true;
  }
  
  function disableOperatorControl(address owner) external onlyRole(OPERATOR_ROLE) whenNotPaused() returns (bool) {
    require(owner != address(0), "ERC721: owner is a zero address");
    require(isOperatorControlled(owner), "ERC721: owner not under operator control");
    _operatorEnabled[owner] = false;
    return true;
  }
  
  function isOperatorControlled(address owner) public view returns (bool) {
    require(owner != address(0), "ERC721: owner is a zero address");
    return _operatorEnabled[owner];
  }

  function supportsInterface(bytes4 interfaceId) public view virtual override(ERC721Upgradeable, AccessControlUpgradeable) returns (bool) {
      return super.supportsInterface(interfaceId);
  }

  function msgSender() internal view returns(address sender) {
    if(msg.sender == _forwarder) {
      bytes memory array = msg.data;
      uint256 index = msg.data.length;
      assembly {
          sender := and(mload(add(array, index)), 0xffffffffffffffffffffffffffffffffffffffff)
      }
    } else {
        sender = msg.sender;
    }
    return sender;
  }

  function setForwarder(address forwarder) external onlyRole(OPERATOR_ROLE) returns (bool) {
    _forwarder = forwarder;
    return true;
  }
  
  function getForwarder() external view returns (address) {
    return _forwarder;
  }
}

额外注意事项

  • 确保安装了@openzeppelin/contracts-upgradeable包,可通过npm install @openzeppelin/contracts-upgradeable安装
  • 部署可升级合约时要使用Truffle的升级插件(如@openzeppelin/truffle-upgrades),不能用普通部署方式

内容的提问来源于stack exchange,提问作者Golanger

相关产品推荐
方舟 Agent Plan

超全模态模型 × Harness 升级,最新支持 Deepseek-V4.1-Flash、GLM-5.3 系列、Doubao-Seedream-5.0-pro、Kimi-K3 (部分), 限时 9.9 元起

最近更新时间:2026.08.11 07:20:35