实现“public onlyOwner”的 Solidity 函数甚至不能被所有者调用

Solidity function implementing `public onlyOwner` cannot be called even by the owner

我正在关注此处的文档: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;
     }    

  }

当我尝试估算 minting 的 gas 成本时:

    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 安全帽编译 npx 安全帽 运行 scripts/deploy.js

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

附录

alchemy 给出的用于部署 mint 的确切代码是:

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 相同,在这种情况下 nftContractpublic onlyOwner 指定的。这正是我所做的。那么从概念上讲,谁拥有这个 NFT 代码?在 etherscan 上是 to 地址(合约地址),还是 from 地址,这是我的 public 密钥,部署合约的地址,以及调用 mint 的地址,现在因 caller is not the owner 错误而失败。

上网搜索,我看到其他人在这里遇到过这个问题:https://ethereum.stackexchange.com/questions/94114/erc721-testing-transferfrom,对于Truffle你可以用额外的字段指定调用者:

 await nft.transferFrom(accounts[0], accounts[1], 1, { from: accounts[1] })

这里不能选择额外的参数,因为我使用的是安全帽。

OpenZeppelin 的 Ownable.sol 将默认 owner 值定义为合约部署者。您稍后可以通过调用 transferOwnership() 来更改它,或者通过调用 renounceOwnership().

放弃所有者(即设置为 0x0

如果当前 owner 未发送交易,onlyOwner 修饰符将还原交易。 (see the code)

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

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

我终于想通了,合约没有按照我部署的方式初始化。所以你必须在部署后对其进行初始化。

为使用 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 的 mint 函数上调用 estimateGas() 时,它将能够使用该 from 字段来查看调用 estimateGas 的所有者。

花了很长时间才弄明白。