我正在编写一个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;
}
}
这里我有两个公共函数:owner
和mintNFT
都返回一些值。在我的测试中,我想读取来自这两个函数的返回值。以下是我在Hardhat上运行的测试:
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);
});
});
});
在owner
函数的情况下,我得到了我所期望的:它返回我的帐户地址,并且第一次测试成功通过。然而,当涉及到mintNFT
功能时,我没有得到我所期望的:而不是新创建的项目ID,我得到了一些非常不同的东西,我的第二次测试失败了。
为什么两个非常相似的测试给我不同的结果?我如何从发送事务的函数中获得返回值?作为参考,这是我使用的hardhat.config.js
文件:
require("@nomiclabs/hardhat-waffle");
module.exports = {
solidity: "0.8.4",
defaultNetwork: 'rskregtest',
networks: {
rskregtest: {
chainId: 33,
url: 'http://localhost:4444',
},
},
};
你不能直接接收一个函数的返回值,你正在发送一个交易到链下。你只能在链上这样做——即当一个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);
在您已经在hardhat测试中使用的Ethers.js中,您将获得一行返回的事务响应对象,而不是智能合约函数返回值:
const txResponse = await myNFT.mintNFT(deployer.address, newMintItem.uri);
接下来,您需要通过在txResponse
上调用wait
来获得交易收据:
const txReceipt = await txResponse.wait();
反过来,txReceipt
包含一个events
数组,其中包含事务发出的所有事件。在这种情况下,期望它包含一个单一的Transfer
事件,表明第一个铸造的物品从零地址转移到您的帐户地址。通过从events
数组中提取第一个元素来获取此事件:
const [transferEvent] = txReceipt.events;
接下来从transferEvent
参数中获取tokenId
属性,这将是新生成的NFT的Id:
const { tokenId } = transferEvent.args;
整个测试应该如下所示:
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://QmY6KX35Rg25rnaffmZzGUFb3raRhtPA5JEFeSSWQA4GHL',
};
const txResponse = await myNFT.mintNFT(deployer.address, newMintItem.uri);
const txReceipt = await txResponse.wait();
const [transferEvent] = txReceipt.events;
const { tokenId } = transferEvent.args;
expect(tokenId).to.equal(newMintItem.id);
});
});
从事务返回的值在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;
}
}
it('Should return a correct ID of the newly minted item', async () => {
const newMintItem = {
id: 1,
uri: 'ipfs://Qme3QxqsJih5psasse4d2FFLFLwaKx7wHXW3Topk3Q8b14',
};
// testing the emitted event
await expect(myNFT.mintNFT(deployer.address, newMintItem.uri))
.to.emit(myNFT, "NFTMinted")
.withArgs(newMintItem.id);
// testing the getter value
const counter = await myNFT._counter();
expect(counter).to.equal(newMintItem.id);
});
文档:https://ethereum-waffle.readthedocs.io/en/latest/matchers.html发送事件