如何接收Solidity智能合约交易函数返回的值?

Ahs*_*san 11 solidity rsk ethers.js hardhat

我正在编写一个NFT 智能合约,我将通过Hardhat对其进行测试并部署在RSK上。

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract MyNFT is ERC721URIStorage {
    uint private _counter;
    address private _owner;

    constructor() ERC721("My NFT", "MNFT") {
      _owner = msg.sender;
    }

    function owner() public view returns (address) {
      return _owner;
    }

    function mintNFT(address recipient, string memory tokenURI)
        public returns (uint256)
    {
        require(msg.sender == owner(), "Only owner is allowed to mint");
        uint newItemId = ++_counter;
        ERC721._mint(recipient, newItemId);
        ERC721URIStorage._setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}
Run Code Online (Sandbox Code Playgroud)

这里我有两个公共函数:owner并且mintNFT都返回一些值。在我的测试中,我想读取来自这两个函数的返回值。这些是我在 Hardhat 上运行的测试:

//SPDX-License-Identifier: Unlicense
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/token/ERC721/extensions/ERC721URIStorage.sol";

contract MyNFT is ERC721URIStorage {
    uint private _counter;
    address private _owner;

    constructor() ERC721("My NFT", "MNFT") {
      _owner = msg.sender;
    }

    function owner() public view returns (address) {
      return _owner;
    }

    function mintNFT(address recipient, string memory tokenURI)
        public returns (uint256)
    {
        require(msg.sender == owner(), "Only owner is allowed to mint");
        uint newItemId = ++_counter;
        ERC721._mint(recipient, newItemId);
        ERC721URIStorage._setTokenURI(newItemId, tokenURI);

        return newItemId;
    }
}
Run Code Online (Sandbox Code Playgroud)

在该owner函数的情况下,我得到了我所期望的结果:它返回我的帐户地址,并且第一个测试成功通过。然而,当涉及到该mintNFT函数时,我没有得到我所期望的结果:我得到的不是新创建的项目 ID,而是非常不同的东西,并且我的第二次测试失败了。

为什么两个非常相似的测试给出不同的结果?如何从发送交易的函数获取返回值?作为参考,这是hardhat.config.js我正在使用的文件:

const { expect } = require("chai");
const { ethers } = require("hardhat");

describe("My NFT", () => {
  let deployer;
  let myNFT;

  // deploy NFT before the tests
  before(async () => {
    [deployer] = await ethers.getSigners();
    const MyNFT = await ethers.getContractFactory('MyNFT');
    myNFT = await MyNFT.deploy();
    await myNFT.deployed();
  });

  describe('Receiving a value returned by a view function', () => {
    it('The deployer should be the s/c owner', async  () => {
      const owner = await myNFT.owner();
      expect(owner).to.equal(deployer.address);
    });
  });
  
  describe('Receiving a value returned by a transacting function', () => {
    it('Should return a correct ID of the newly minted item', async () => {
      const newMintItem = {
        id: 1,
        uri: 'ipfs://Qme3QxqsJih5psasse4d2FFLFLwaKx7wHXW3Topk3Q8b14',
      };
      const newItemId = await myNFT.mintNFT(deployer.address, newMintItem.uri);
      expect(newItemId).to.equal(newMintItem.id);
    });
  });
});
Run Code Online (Sandbox Code Playgroud)

Pet*_*jda 14

从交易返回的值在 EVM 之外不可用。

您可以发出事件,或创建view该值的公共 getter 函数。

contract MyNFT is ERC721URIStorage {
    // `public` visibility autogenerates view function named `_counter()`
    uint public _counter;
    event NFTMinted(uint256 indexed _id);

    function mintNFT(address recipient, string memory tokenURI)
        public returns (uint256)
    {
        // ...
        emit NFTMinted(newItemId);
        return newItemId;
    }
}
Run Code Online (Sandbox Code Playgroud)
contract MyNFT is ERC721URIStorage {
    // `public` visibility autogenerates view function named `_counter()`
    uint public _counter;
    event NFTMinted(uint256 indexed _id);

    function mintNFT(address recipient, string memory tokenURI)
        public returns (uint256)
    {
        // ...
        emit NFTMinted(newItemId);
        return newItemId;
    }
}
Run Code Online (Sandbox Code Playgroud)

文档:https://ethereum-waffle.readthedocs.io/en/latest/matchers.html#emitting-events


Ale*_*hin 11

您无法直接接收来自将交易发送到链外的函数的返回值。您只能在链上执行此操作 - 即当一个 SC 函数调用另一个 SC 函数时。

但是,在这种特殊情况下,您可以通过事件获取该值,因为ERC721 规范包含一个Transfer事件:

    /// @dev This emits when ownership of any NFT changes by any mechanism.
    ///  This event emits when NFTs are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of NFTs
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
Run Code Online (Sandbox Code Playgroud)

在您已在安全帽测试中使用的Ethers.js中,您将获得此行返回的交易响应对象,而不是智能合约函数返回值:

    /// @dev This emits when ownership of any NFT changes by any mechanism.
    ///  This event emits when NFTs are created (`from` == 0) and destroyed
    ///  (`to` == 0). Exception: during contract creation, any number of NFTs
    ///  may be created and assigned without emitting Transfer. At the time of
    ///  any transfer, the approved address for that NFT (if any) is reset to none.
    event Transfer(address indexed _from, address indexed _to, uint256 indexed _tokenId);
Run Code Online (Sandbox Code Playgroud)

接下来,您需要通过以下方式获取交易收据waittxResponse

const txResponse = await myNFT.mintNFT(deployer.address, newMintItem.uri);
Run Code Online (Sandbox Code Playgroud)

反过来,txReceipt包含一个events数组,其中包含您的事务发出的所有事件。在这种情况下,预计它会包含一个Transfer事件,指示第一个铸造项目从零地址转移到您的帐户地址。通过从数组中提取第一个元素来获取此事件events

const txReceipt = await txResponse.wait();
Run Code Online (Sandbox Code Playgroud)

接下来从参数中获取tokenId属性transferEvent,这将是您新创建的 NFT 的 ID:

const [transferEvent] = txReceipt.events;
Run Code Online (Sandbox Code Playgroud)

整个测试应如下所示:

const { tokenId } = transferEvent.args;
Run Code Online (Sandbox Code Playgroud)