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

调用ERC1155合约mint函数触发Ownable权限错误求助

问题排查:Ownable权限验证失败问题

问题背景

部署了集成Ownable、可铸造(mintable)、可销毁(burnable)的ERC1155合约DTicket,通过Etherscan及合约调用确认自身EOA地址为合约owner;使用Web3.js签名发送交易时,ETH转账测试正常,但调用带onlyOwner修饰的mint函数时,触发Eip838ExecutionError,报错信息为execution reverted: Ownable: caller is not the owner。

错误详情

sending transaction, error occured.
Eip838ExecutionError: execution reverted: Ownable: caller is not the owner
innerError: [Eip838ExecutionError: execution reverted: Ownable: caller is not the owner] {
innerError: undefined,
code: 3,
receipt: undefined,
data: ''
},
code: 310,
receipt: undefined
}

合约代码

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

import "@openzeppelin/contracts/token/ERC1155/ERC1155.sol";
import "@openzeppelin/contracts/token/ERC1155/extensions/ERC1155Burnable.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

contract DTicket is ERC1155, ERC1155Burnable, Ownable {
    constructor(address initialOwner) ERC1155("") Ownable(initialOwner) {}

    function mint(address account, uint256 id, uint256 amount, bytes memory data)
        public
        onlyOwner
    {
        _mint(account, id, amount, data);
    }

    function mintBatch(address to, uint256[] memory ids, uint256[] memory amounts, bytes memory data)
        public
        onlyOwner
    {
        _mintBatch(to, ids, amounts, data);
    }
}

编译代码

// This code will compile smart contract and generate its ABI and bytecode
// Alternatively, you can use something like `npm i solc && npx solcjs MyContract.sol --bin --abi`

const solc = require('solc');
const path = require('path');
const fs = require('fs');

const fileName = 'DTicket.sol';
const contractName = 'DTicket';

// Read the Solidity source code from the file system
const contractPath = path.join(__dirname, fileName);
const sourceCode = fs.readFileSync(contractPath, 'utf8');

// solc compiler config
const input = {
    language: 'Solidity',
    sources: {
        [fileName]: {
            content: sourceCode,
        },
    },
    settings: {
        outputSelection: {
            '*': {
                '*': ['*'],
            },
        },
    },
};

// Compile the Solidity code using solc
const compiledCode = JSON.parse(solc.compile(JSON.stringify(input)));
//console.log(compiledCode);
// Get the bytecode from the compiled contract
const bytecode = compiledCode.contracts[fileName][contractName].evm.bytecode.object;

// Write the bytecode to a new file
const bytecodePath = path.join(__dirname, 'DTicketBytecode.bin');
fs.writeFileSync(bytecodePath, bytecode);

// Log the compiled contract code to the console
console.log('Contract Bytecode:\n', bytecode);

// Get the ABI from the compiled contract
const abi = compiledCode.contracts[fileName][contractName].abi;

// Write the Contract ABI to a new file
const abiPath = path.join(__dirname, 'DTicketAbi.json');
fs.writeFileSync(abiPath, JSON.stringify(abi, null, '\t'));

// Log the Contract ABI to the console
console.log('Contract ABI:\n', abi);

交易发送代码

// First step: initialize `web3` instance
const {Web3} = require('web3');
const { ETH_DATA_FORMAT, DEFAULT_RETURN_FORMAT } = require('web3');
const fs = require('fs');
const path = require('path');

require("dotenv").config();
// Set up a connection to the Ethereum network
const network = process.env.ETHEREUM_NETWORK;
const web3 = new Web3(
    new Web3.providers.HttpProvider(
      `https://${network}.infura.io/v3/${process.env.INFURA_API_KEY}`,
    ),
);

const sendTx = async () => {
  const accountFrom = {
    privateKey: process.env.SIGNER_PRIVATE_KEY,
    address: process.env.SIGNER_ADDRESS
  };
  
  // 4. Create Contract Instance
  const abi = require('./DTicketAbi.json');
  const contractor = new web3.eth.Contract(abi, process.env.CONTRACT_ADDRESS);
  var mintAccount = accountFrom.address;
  var mintId = 0;
  var mintAmount = 100;
  var mintData = web3.utils.utf8ToHex('1');
  const mint = contractor.methods.mint(mintAccount, mintId, mintAmount, mintData);

  console.log(`Calling the reset function in contract at address: ${process.env.CONTRACT_ADDRESS}`);

  let limit;
  await web3.eth
    .estimateGas(
      {
        from: accountFrom.address,
        to: process.env.CONTRACT_ADDRESS,
        data: mint.encodeABI()
      },
      "latest",
      ETH_DATA_FORMAT,
    )
    .then((value) => {
      limit = value;
    });

  // 7. Sign tx with PK
  const createTransaction = await web3.eth.accounts.signTransaction(
    {
      to: process.env.CONTRACT_ADDRESS,
      data: mint.encodeABI(),
      value: '0x0',
      nonce: await web3.eth.getTransactionCount(accountFrom.address),
      maxPriorityFeePerGas: web3.utils.toWei("3", "gwei"),
      maxFeePerGas: web3.utils.toWei("3", "gwei"),
    },
    accountFrom.privateKey
  );
   // 8. Send tx and wait for receipt
   const createReceipt = await web3.eth.sendSignedTransaction(createTransaction.rawTransaction);
   console.log(`Tx successful with hash: ${createReceipt.transactionHash}`);
};

async function interact() {
  web3.eth.Contract.handleRevert = true;

  // Read the contract address from the file system
  const deployedAddressPath = path.join(__dirname, 'DTicketAddress.bin');
  const deployedAddress = fs.readFileSync(deployedAddressPath, 'utf8');
  // Create a new contract object using the ABI and bytecode
  const abi = require('./DTicketAbi.json');
  const contract = new web3.eth.Contract(abi, deployedAddress);
  const account = web3.eth.accounts.wallet.add(process.env.SIGNER_PRIVATE_KEY).get(0);
  const defaultAccount = account.address;

  try {
      // Get the current value of my number
      const owner = await contract.methods.owner().call();
      console.log('msgSender: ' + JSON.stringify(owner, null, 4));
      
      sendTx();
  } catch (error) {
      console.error(error);
  }
}

interact();

问题原因

  1. 合约地址不一致:interact函数读取本地文件DTicketAddress.bin获取合约地址,而sendTx函数使用环境变量process.env.CONTRACT_ADDRESS,若两者指向不同合约,会导致调用非目标合约的mint函数,触发权限验证失败。
  2. nonce计算遗漏未确认交易:sendTx中getTransactionCount默认仅统计已确认交易,若存在未确认交易,会导致nonce重复或错误,间接引发交易执行异常。
  3. gasLimit参数缺失:estimateGas得到的gas值未传入签名交易,可能因gas不足导致交易执行异常,虽报错提示权限问题,但需排除此干扰项。

解决方案

  1. 统一合约地址来源:修改sendTx函数,从本地文件读取合约地址,确保和interact中调用owner()的是同一合约:
    // 在sendTx函数中替换合约地址获取逻辑
    const deployedAddressPath = path.join(__dirname, 'DTicketAddress.bin');
    const deployedAddress = fs.readFileSync(deployedAddressPath, 'utf8');
    const contractor = new web3.eth.Contract(abi, deployedAddress);
    
  2. 修正nonce获取方式:添加pending参数,获取包含未确认交易的nonce,避免重复:
    nonce: await web3.eth.getTransactionCount(accountFrom.address, 'pending'),
    
  3. 添加gasLimit参数:将estimateGas得到的limit传入签名交易:
    const createTransaction = await web3.eth.accounts.signTransaction(
      {
        to: deployedAddress,
        data: mint.encodeABI(),
        value: '0x0',
        nonce: await web3.eth.getTransactionCount(accountFrom.address, 'pending'),
        maxPriorityFeePerGas: web3.utils.toWei("3", "gwei"),
        maxFeePerGas: web3.utils.toWei("3", "gwei"),
        gasLimit: limit
      },
      accountFrom.privateKey
    );
    
  4. 核对地址一致性:确认process.env.SIGNER_ADDRESS与Etherscan查询到的合约owner地址完全一致(以太坊地址大小写不敏感,但建议保持格式统一)。

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

相关产品推荐
方舟 Agent Plan

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

最近更新时间:2026.07.08 23:58:10