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

基于@imtbl/zkevm-contracts部署NFT至IMX zkEVM遇执行回滚错误

部署IMX zkEVM NFT合约时执行回滚错误排查

错误信息

error: ProviderError: unable to apply transaction even for the highest gas limit 30000000: execution reverted
      at HttpProvider.request (C:\src\gumi\contracts\node_modules\hardhat\src\internal\core\providers\http.ts:88:21)      
      at processTicksAndRejections (node:internal/process/task_queues:96:5)
      at async EthersProviderWrapper.send (C:\src\gumi\contracts\node_modules\@nomiclabs\hardhat-ethers\src\internal\ethers-provider-wrapper.ts:13:20)

部署脚本

/* eslint-disable prettier/prettier */
/* eslint-disable dot-notation */
/* eslint-disable spaced-comment */
/* eslint-disable no-unused-vars */
/* eslint-disable camelcase */
/* eslint-disable node/no-missing-import */
/* eslint-disable no-process-exit */
import * as dotenv from "dotenv";
import { ethers } from "hardhat";
import { craft__factory } from "../typechain";
import { insertRecord } from "../build_artifact";
import fs from 'fs'

dotenv.config();

// get baseURI from .env
const BASE_URI = process.env.BASE_URI!;

async function main() {
  const signers = await ethers.getSigners();

  const address = signers[0];

  const contractURI = "ipfs";
  const operatorAllowlist = address.address;
  console.log(address.address, BASE_URI, contractURI, operatorAllowlist);


  const factory = new craft__factory(address);
  const contract = await factory.deploy(address.address, BASE_URI, contractURI, operatorAllowlist);
  const nft = await contract.deployed();
    
  const data =  fs.readFileSync("artifacts/src/Gumicraft.sol/Gumicraft.json", 'utf8');
  const json = JSON.parse(data);
  const name = "Gumicraft";
  console.log(`${name} deployed to: ${nft.address}\nwith artifact ${json}`);
  await insertRecord([name, nft.address, json]);
}

main()
  .then(() => process.exit(0))
  .catch((error) => {
    console.error(error);
    process.exit(1);
  });

NFT合约代码

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.17;

import "../base/NFT.sol";


contract craft is NFT {

    // a constructor that takes a baseURI, an imxContract and an array of defaultOperators
     constructor( 
        address owner_,
        string memory baseURI_,
        string memory contractURI_,
        address operatorAllowlist_
            )
        NFT(
            owner_,
            "craft",
            "GCRAFT",
            baseURI_,
            contractURI_,
            operatorAllowlist_,
            owner_,
            1000
            ) {
    }
    
}

基础NFT合约代码

// SPDX-License-Identifier: MIT OR Apache-2.0
pragma solidity ^0.8.17;

import {Counters} from "@openzeppelin/contracts/utils/Counters.sol";
import {ReentrancyGuard} from "@openzeppelin/contracts/security/ReentrancyGuard.sol";

import {ImmutableERC721} from "@imtbl/zkevm-contracts/contracts/token/erc721/preset/ImmutableERC721.sol";

abstract contract NFT is ImmutableERC721, ReentrancyGuard {
    event NftMinted(address to, uint256 id, string url);

    struct Nft {
        uint256 id;
        string url;
    }

    using Counters for Counters.Counter;
    Counters.Counter private _tokenIds;
    mapping(uint256 => string) private tokenParameters;

    constructor(
        address owner_,
        string memory name_,
        string memory symbol_,
        string memory baseURI_,
        string memory contractURI_,
        address operatorAllowlist_,
        address receiver_,
        uint96 feeNumerator_
    )
        ImmutableERC721(
            owner_,
            name_,
            symbol_,
            baseURI_,
            contractURI_,
            operatorAllowlist_,
            receiver_,
            feeNumerator_
        )
    {
        
    }

    function setTokenParams(uint256 tokenId, string memory tokenParams)
        public
        virtual
        onlyRole(MINTER_ROLE)
    {
        // validate that string is not empty
        require(
            bytes(tokenParams).length > 0,
            "NFT: token params can not be empty"
        );
        tokenParameters[tokenId] = tokenParams;
    }

    function getTokenFullURL(uint256 tokenId)
        public
        view
        virtual
        returns (string memory)
    {
        return
            string(
                abi.encodePacked(
                    baseURI,
                    contractURI,
                    tokenId,
                    tokenParameters[tokenId]
                )
            );
    }

    function createToken(address to, string memory tokenParams)
        public
        payable
        virtual
        nonReentrant
        onlyRole(MINTER_ROLE)
        returns (uint256)
    {
        _tokenIds.increment();
        uint256 newTokenId = _tokenIds.current();
        super._safeMint(to, newTokenId);
        setTokenParams(newTokenId, tokenParams);

        emit NftMinted(to, newTokenId, getTokenFullURL(newTokenId));
        return newTokenId;
    }

    function getTokensFor(address ownerId) public view returns (Nft[] memory) {
        uint256 balance = balanceOf(ownerId);
        require(balance > 0, "NFT: no tokens for this owner");

        uint256 total = _tokenIds.current();
        Nft[] memory tokens = new Nft[](balance);
        uint256 start = 1;
        
        for (uint256 i = 1; i < total; i++) {
            address _owner = ownerOf(i);
            if (_owner != ownerId) {
                continue;
            } else {
                tokens[start] = Nft(i, getTokenFullURL(i));
                start++;
                if (start == balance) {
                    break;
                }
            }
        }

        return tokens;
    }
}

排查背景

已重构基类、移除构造函数内逻辑调用,但仍收到cannot estimate gas; transaction may fail or may require manual gas limit错误,怀疑合约实例化存在问题。


可能的排查方向及修复方案

  • 构造函数参数有效性检查:
    ImmutableERC721的构造函数可能对参数有隐性约束,比如feeNumerator_通常代表手续费万分比,需确认是否不超过10000(部分实现的上限)。另外,operatorAllowlist_传入部署者地址,需确认该地址是否符合合约对白名单地址的要求,是否需要提前满足特定权限条件。
  • 环境变量验证:
    部署脚本中BASE_URI来自环境变量,若未正确设置会传入空字符串,可能触发ImmutableERC721内部的参数校验。执行部署前务必打印BASE_URI确认其值不为空,且格式符合IPFS/HTTP URI规范。
  • 手动指定Gas参数:
    IMX zkEVM的Gas机制与以太坊主网存在差异,自动估算Gas容易失败。修改部署脚本,在deploy方法中手动指定Gas限制和价格:
    const contract = await factory.deploy(
      address.address, 
      BASE_URI, 
      contractURI, 
      operatorAllowlist,
      { gasLimit: 50000000, gasPrice: ethers.utils.parseUnits("20", "gwei") }
    );
    
  • 依赖版本兼容性排查:
    确认@imtbl/zkevm-contracts版本与Solidity 0.8.17兼容,查看该包的官方文档或仓库说明,确保使用的版本支持当前编译器版本。若存在版本冲突,尝试降级或升级依赖包。
  • 修复getTokensFor函数逻辑错误:
    该函数存在数组索引越界和遍历遗漏问题,虽不影响部署,但会导致后续调用失败:
    • start初始值为1,导致数组第一个元素tokens[0]未赋值,应改为start = 0
    • 循环条件i < total会漏掉最后一个token(当i = total时),应改为i <= total
      修复后的代码:
    function getTokensFor(address ownerId) public view returns (Nft[] memory) {
        uint256 balance = balanceOf(ownerId);
        require(balance > 0, "NFT: no tokens for this owner");
    
        uint256 total = _tokenIds.current();
        Nft[] memory tokens = new Nft[](balance);
        uint256 start = 0;
        
        for (uint256 i = 1; i <= total; i++) {
            address _owner = ownerOf(i);
            if (_owner == ownerId) {
                tokens[start] = Nft(i, getTokenFullURL(i));
                start++;
                if (start == balance) {
                    break;
                }
            }
        }
    
        return tokens;
    }
    
  • 启用Hardhat调试日志:
    在hardhat.config.js中开启详细日志,获取更具体的回滚原因:
    module.exports = {
      solidity: "0.8.17",
      networks: {
        imxZkEVM: {
          url: "YOUR_IMX_ZKEVM_RPC_URL",
          accounts: [process.env.PRIVATE_KEY],
          loggingEnabled: true
        }
      }
    };
    

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.10 01:17:03