此函数已恢复,原因字符串 'ERC721: transfer caller is not owner nor approved'

This function reverted with reason string 'ERC721: transfer caller is not owner nor approved'

代码:

function f(address nftContract, uint256 itemId, uint256 price) public payable nonReentrant 
{
  uint tokenId = idToMarketItem[itemId].tokenId;
  IERC721(nftContract).approve(address(this), tokenId);
  IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
}

我真的不知道怎么了。请帮助我

修复方法是不在您的函数中进行批准:

function f(address nftContract, uint256 itemId, uint256 price) public payable nonReentrant 
{
  uint tokenId = idToMarketItem[itemId].tokenId;
  IERC721(nftContract).transferFrom(msg.sender, address(this), tokenId);
}

因为你试图让你的合同批准自己花费属于调用者的资金(参见我在评论中链接的答案,这解释了为什么 msg.sender 不是你在 IERC721(nftContract ).approve call) 这将不起作用。用户必须直接在 nftContract 上调用 approve。

这是用户的nft,只是它应该有权通过第三方批准他们的消费,没有其他人。

编辑:添加更完整的示例/解释。

我们来看一个简单的 ERC721 合约:

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.0 <0.9.0;

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

contract myToken is ERC721 {

    constructor() ERC721("TOKEN NAME", "TOKEN SYMBOL") {
    }

    function mint(uint256 tokenId) public {
        _mint(msg.sender, tokenId);
    }
}

它使用我们的特殊函数 mint(uint256 tokenId) 公开所有 ERC-721 函数。

现在,为了更接近您的用例,我们来看一个示例智能合约,它将尝试通过批准和 transferFrom 的方式转移属于用户的 NFT。

// SPDX-License-Identifier: GPL-3.0

pragma solidity >=0.8.0 <0.9.0;

import "./token.sol";

contract myContract {

    myToken _tokenContract;


    constructor(myToken tokenContract) {
        _tokenContract = tokenContract;
    }

    function requestTransfer(address from, address to, uint256 tokenId) public{
        _tokenContract.transferFrom(from, to, tokenId);
    }
}

我们在部署时将 NFT 合约的地址发送给构造函数。我们唯一感兴趣的函数是 requestTransfer 函数。

现在让我添加一个松露测试文件(远非完美):

const truffleAssert = require('truffle-assertions');
const tokenContract = artifacts.require("myToken");
const smartContract = artifacts.require("myContract");

contract("myContract", accounts => {
  it("shound hook the deployed token contract", async () => {
    const tokenInstance = await tokenContract.deployed();
  });


  it("shound hook the deployed smart contract", async () => {
    const  contractInstance = await smartContract.deployed();
  });

  it("should mint 1 NFT", async () => {
    const tokenInstance = await tokenContract.deployed();
    const txResult = await tokenInstance.mint(0, {from: accounts[0]});
    truffleAssert.eventEmitted(txResult, "Transfer", (event) => 
        {
            return event.tokenId == 0;
        })
    });

  it("Should fail because smart contract was not approved", async () => {
    const contractInstance = await smartContract.deployed();
    const tokenId = 0;
    let failed = false;

    try {
    const txResult = await contractInstance.requestTransfer(accounts[0], accounts[1], tokenId);   
    }
    catch (error) {
        failed = true;
    }

    assert.equal(failed, true, "This test should have failed");
  })

  it("Approves the smart contract to transfer accounts[0] token : (tokenId = 0)", async () => {
    const contractInstance = await smartContract.deployed();
    const tokenInstance = await tokenContract.deployed();
    const tokenId = 0;
    const approved = contractInstance.address;
    const txResult = await tokenInstance.approve(contractInstance.address, tokenId, {from: accounts[0]});

    truffleAssert.eventEmitted(txResult, "Approval", (event) => 
    {
        return event.tokenId == 0;
    })
  });

  it("Should work because smart contract was approved", async () => {
    const contractInstance = await smartContract.deployed();
    const tokenId = 0;
    let succeded = false;

    try {
    const txResult = await contractInstance.requestTransfer(accounts[0], accounts[1], tokenId);
    succeded = true;
    }
    catch (error) {
        console.log(error);
    }

    assert.equal(succeded, true, "This test should have succeded");
  })
});

如果您遵循测试:

  1. 检查代币合约的部署

  2. 检查代币合约的部署

  3. 为帐户[0]

    铸造 1 个 NFT,tokenId == 0
  4. 调用了我们智能合约的 requestTransfer 方法,但正如预期的那样失败了。因为没有批准我们的智能合约能够管理属于 accounts[0]

    的 tokenId 0
  5. 从帐户[0] 地址直接对代币合约进行批准。允许我们的智能合约地址管理 tokenId 0.

  6. 调用4)中的requestTransfer方法,这次预计会成功,因为已获得适当的批准。

只有所有者才能批准第三方管理其资产。 我希望现在清楚为什么测试 4) 失败了。为什么需要 5) 才能使 6) 成功。另外现在你有一个从 JavaScript 调用 approve 的例子,这有点类似于客户端(NFT 所有者)端应该发生的事情。