实现`public onlyOwner`的Solidity函数,即使由所有者调用也无法被调用

7

我正在按照这里的文档进行操作:https://docs.alchemyapi.io/alchemy/tutorials/how-to-create-an-nft/how-to-mint-a-nft。并且有一个如下形式的智能合约:

pragma solidity ^0.8.0;
import "@openzeppelin/contracts/token/ERC721/ERC721.sol";
import "@openzeppelin/contracts/utils/Counters.sol";
import "@openzeppelin/contracts/access/Ownable.sol";

  contract NFTA is ERC721, Ownable {

     using Counters for Counters.Counter;
     Counters.Counter public _tokenIds;
     mapping (uint256 => string) public _tokenURIs;
     mapping(string => uint8) public hashes;

     constructor() public ERC721("NFTA", "NFT") {}

     function mintNFT(address recipient, string memory tokenURI)
          public onlyOwner
          returns (uint256)
      {
          _tokenIds.increment();

          uint256 newItemId = _tokenIds.current();
          _mint(recipient, newItemId);
          _setTokenURI(newItemId, tokenURI);

          return newItemId;
     }

     /**
      * @dev Sets `_tokenURI` as the tokenURI of `tokenId`.
      *
      * Requirements:
      *
      * - `tokenId` must exist.
      */
     function _setTokenURI(uint256 tokenId, string memory _tokenURI) internal virtual {
        require(_exists(tokenId), "ERC721URIStorage: URI set of nonexistent token");
        _tokenURIs[tokenId] = _tokenURI;
     }    

  }

当我尝试使用以下代码估算“铸造”所需的燃气成本时:

    const MY_PUBLIC_KEY  = '..'
    const MY_PRIVATE_KEY = '..'

    const ALCHEMY = {
        http: '',
        websocket:'',
    }

    const { createAlchemyWeb3 } = require("@alch/alchemy-web3");
    const web3 = createAlchemyWeb3(ALCHEMY.http);

    const NFTA = require("../artifacts/contracts/OpenSea.sol/NFTA.json");
    const address_a   = '0x...';
    const nft_A = new web3.eth.Contract(NFTA.abi, address_a);


    async function mint({ tokenURI, run }){

        const nonce = await web3.eth.getTransactionCount(MY_PUBLIC_KEY, 'latest'); 
        const fn  = nft_A.methods.mintNFT(MY_PUBLIC_KEY, '')

        console.log( 'fn: ', fn.estimateGas() )
    }

    mint({ tokenURI: '', run: true })

我收到错误:

(node:29262) UnhandledPromiseRejectionWarning: Error: Returned error: execution reverted: Ownable: caller is not the owner

由于mintNFTpublic onlyOwner,所以可能会出现这种情况。但是,当我检查Etherscan时,From字段与MY_PUBLIC_KEY相同,我不确定还能做什么来将交易标记为来自MY_PUBLIC_KEY。解决这个问题的简单方法是从function mintNFT中删除onlyOwner,然后一切都按预期运行。但是假设我们想保留onlyOwner,我该如何签署交易超出上面已经写的内容呢?
注意,我使用hardHat编译合同并部署它们。即: npx hardhat compile npx hardhat run scripts/deploy.js

=============================================

附录

炼金术提供的精铸部署代码如下:

async function mintNFT(tokenURI) {
  const nonce = await web3.eth.getTransactionCount(PUBLIC_KEY, 'latest'); //get latest nonce

  //the transaction
  const tx = {
    'from': PUBLIC_KEY,
    'to': contractAddress,
    'nonce': nonce,
    'gas': 500000,
    'data': nftContract.methods.mintNFT(PUBLIC_KEY, tokenURI).encodeABI()
  };

请注意,在交易中,from字段是PUBLIC_KEY,与部署合同的相同的PUBLIC_KEY,在这种情况下,nftContract指定了public onlyOwner。 这正是我所做的。 因此,从概念上讲,谁拥有这个NFT代码? 在etherscan上,它是to地址(合同地址),还是from地址,即我的公钥,部署合同的地址,并且正在调用铸造,现在因为调用者不是所有者而失败。 enter image description here 搜索互联网,我看到其他人在这里遇到了这个问题:https://ethereum.stackexchange.com/questions/94114/erc721-testing-transferfrom,对于Truffle,您可以使用额外的字段指定调用方。
 await nft.transferFrom(accounts[0], accounts[1], 1, { from: accounts[1] })

这里不考虑额外参数,因为我正在使用 hardhat。


当你调用 methods.mintNFT 时,我猜你需要传递测试账户发送地址。 - Mikko Ohtamaa
@MikkoOhtamaa 我该怎么做?测试账户的发件人地址和我的Metamask钱包公钥一样吗? - xiaolingxiao
3个回答

6

OpenZeppelin的Ownable.sol将默认的owner值定义为合约部署者。您可以通过调用transferOwnership()来稍后更改它,或通过调用renounceOwnership()放弃所有权(即设置为0x0)。

onlyOwner修改器将在交易不是由当前owner发送时回滚交易。请参见代码

因此,您需要使用与部署合同相同的地址调用mintNFT()函数,因为那是当前的owner。或者,您可以首先调用transferOwnership()(从当前的owner地址)来更改owner

mintNFT()函数中删除onlyOwner修改器将允许任何人调用该函数。


这是问题,我只有一个公钥在流通,难道不应该是部署合约的地址吗? - xiaolingxiao
1
从您的截图中,我可以看到合约0xa26c...是由地址0xd2590...部署的,有效地使该地址成为“所有者”。并且该地址能够执行mintNFT()函数。 - Petr Hejda
1
看起来你的 MY_PUBLIC_KEY 的值不是 0xd2590...,这会导致交易被撤销,只有 owner 才能执行它。同时注意 MY_PUBLIC_KEY 的值可能与你第二段代码中的 PUBLIC_KEY 不同。但我无法验证它,因为你的问题没有显示这些值。 - Petr Hejda
1
我移除了onlyOwner,我没有注意到 - 我的错...但现在,我已经没有更多的想法了。希望有人能够更好地帮助你。 - Petr Hejda
1
他们必须使用与“所有者”公钥配对的私钥来签署交易。 - Petr Hejda
显示剩余4条评论

3
回答这个问题是为了帮助在使用Alchemy教程时遇到问题的其他人:
在教程中,它说要在您的mint方法中初始化合约,例如:
const contract = require("../artifacts/contracts/MyNFT.sol/MyNFT.json");
const contractAddress = "0x81c587EB0fE773404c42c1d2666b5f557C470eED";
const nftContract = new web3.eth.Contract(contract.abi, contractAddress);

然而,如果您尝试调用estimateGas()或encodeABI(),它将因onlyOwner错误而失败。
解决方案是将第三行更改为:
const nftContract = new web3.eth.Contract(contract.abi, contractAddress, {
    from: PUBLIC_KEY
});

这将设置默认值为“From”,因此当您调用标记为onlyOwner的铸造函数的estimateGas()时,它将能够使用该from字段来查看其所有者是否正在调用estimateGas。

花了很长时间才弄清楚这一点。


2
非常感谢!您还可以直接将 from: PUBLIC_KEY 添加到传递给 estimateGas() 的数据中:const estimatedGas = await web3.eth.estimateGas({ from: PUBLIC_KEY, to: contractAddress, ...}) - Casey L

1

我终于明白了,合约在部署时没有初始化。因此,必须在部署后进行初始化。


网页内容由stack overflow 提供, 点击上面的
可以查看英文原文,
原文链接