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

标签 solidity

代码:

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 调用中所想的那样)这不会起作用。用户必须直接调用 nftContract 上的批准。

这是用户的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 个带有 tokenId == 0 的 NFT

  4. 调用我们智能合约的 requestTransfer 方法并按预期失败。因为没有批准我们的智能合约能够管理属于accounts[0]的tokenId 0

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

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

只有所有者才批准第三方管理其 Assets 。 我希望现在已经清楚为什么测试 4) 失败了。为什么 6) 成功需要 5)。另外,现在您有一个从 JavaScript 调用批准的示例,这与客户端(NFT 所有者)端发生的情况有些相似。

关于solidity - 此函数已恢复,原因字符串为 'ERC721: transfer caller is not owner nor approved',我们在Stack Overflow上找到一个类似的问题: https://stackoverflow.com/questions/69779171/

相关文章:

testing - 如何解决所有“hook : prepare suite:"之前的错误 ""”

ethereum - 如何在 Solidity 中比较 ascii 字符串和 uint8 数组?

solidity - 使用 ethers js 创建数据交易

blockchain - 从 web3js 中的 Solidity 函数访问多个返回值(a、b、c)

blockchain - 关于如何使用 Quorum 区 block 链的一般问题

javascript - web3.eth.accounts[0] 返回未定义

node.js - 如何通过指定地址以太坊获取待处理交易?

Solidity 错误 : Expected identifier, 得到 'LParen'

solidity - RSK Bridge 智能合约的 Solidity 接口(interface)是什么?

blockchain - ERC20 标准中的批准和允许方法真正在做什么?