调用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();
问题原因
- 合约地址不一致:
interact函数读取本地文件DTicketAddress.bin获取合约地址,而sendTx函数使用环境变量process.env.CONTRACT_ADDRESS,若两者指向不同合约,会导致调用非目标合约的mint函数,触发权限验证失败。 - nonce计算遗漏未确认交易:
sendTx中getTransactionCount默认仅统计已确认交易,若存在未确认交易,会导致nonce重复或错误,间接引发交易执行异常。 - gasLimit参数缺失:
estimateGas得到的gas值未传入签名交易,可能因gas不足导致交易执行异常,虽报错提示权限问题,但需排除此干扰项。
解决方案
- 统一合约地址来源:修改
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); - 修正nonce获取方式:添加
pending参数,获取包含未确认交易的nonce,避免重复:nonce: await web3.eth.getTransactionCount(accountFrom.address, 'pending'), - 添加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 ); - 核对地址一致性:确认
process.env.SIGNER_ADDRESS与Etherscan查询到的合约owner地址完全一致(以太坊地址大小写不敏感,但建议保持格式统一)。
内容的提问来源于stack exchange,提问作者ggrrm
相关产品推荐
相关产品推荐

