체인의정석

ERC721의 safeTransferFrom 본문

블록체인/NFT & BRIDGE

ERC721의 safeTransferFrom

체인의정석 2022. 2. 14. 11:41
728x90
반응형

safeTransferFrom 같은 경우 

ERC721 onRECEIVE를 상속 받았는지 여부를 체크해 준다.

 

스마트컨트렉트가 받는 주소라면 이렇게 transferFrom을 사용하여야지 안에 토큰들이 갇히는 걸 방지할 수 있다.

기본적으로 오픈제플린에서 상속을 받으면 safeTransferFrom 이 적용된다.

 

하지만 직접적으로 토큰을 다루는 컨트렉트 들은 

  function onERC721Received(
      address,
      address,
      uint256,
      bytes memory
  )
      public
      returns (bytes4)
  {
      return this.onERC721Received.selector;
  }

"IERC721Receiver" 인터페이스를  상속 받은 후 위와 같이 오버라이딩 해주어야 한다.

위는 0.5.0 버전으로 하엿지만 최신 버전에는 override를 넣어준다.

 

이러한 작업을 해서 해당 컨트렉트가 Receiver를 구현했을 경우에만 토큰을 보내면 보낼때 만약 transferFrom으로 받는 함수가 있어야지만 받는다. 실제로 transferFrom과 safeTransferFrom은 위의 인터페이스를 구현했는지 여부를 두며, 위의 인터페이스에서는 transferFrom에 대한 함수가 바이트로 변환되어 들어가 있기 때문에. 만약, 해당 함수가 있을 경우라면 실행을 해주고 위와 같이 Receiver가 체크를 안해주면 실행이 안되는 원리이다.

 

pragma solidity ^0.5.0;

/**
 * @title ERC721 token receiver interface
 * @dev Interface for any contract that wants to support safeTransfers
 * from ERC721 asset contracts.
 */
contract IERC721Receiver {
    /**
     * @notice Handle the receipt of an NFT
     * @dev The ERC721 smart contract calls this function on the recipient
     * after a `safeTransfer`. This function MUST return the function selector,
     * otherwise the caller will revert the transaction. The selector to be
     * returned can be obtained as `this.onERC721Received.selector`. This
     * function MAY throw to revert and reject the transfer.
     * Note: the ERC721 contract address is always the message sender.
     * @param operator The address which called `safeTransferFrom` function
     * @param from The address which previously owned the token
     * @param tokenId The NFT identifier which is being transferred
     * @param data Additional data with no specified format
     * @return bytes4 `bytes4(keccak256("onERC721Received(address,address,uint256,bytes)"))`
     */
    function onERC721Received(address operator, address from, uint256 tokenId, bytes memory data)
    public returns (bytes4);
}

위와 같이 오픈제플린의 주석을 보면 retruned를 obtain하는 방법이 나와 있어 맨 위에 있는 코드와 같이 리턴을 해주는 것이다.

 

또한 safeTransferFrom은 ethers.js에서 구현할 때 다른 함수와는 조금 다른 방식을 취하는데 그 이유는 함수를 오버라이딩 된걸 받아오기 때문이다.

 

ethers.js에서 safeTransferFrom을 다루는 것은 

 

https://forum.openzeppelin.com/t/where-is-safefromtransfer-function-in-contract-instance/12020

 

Where is safeFromTransfer function in contract instance?

I create a contract instance in hardhat console like so: const contract_fac = await ethers.getContractFactory("ContractName"); const contract = await contract_fac.attach("CONTRACTADDR..."); But contract object has all public/external functions except safeT

forum.openzeppelin.com

위와 같이 다른것과 다르게 오버로딩 된 함수이므로 

Wrong:
contract.safeTransferFrom(addr1, addr2, 1);

Correct:
contract["safeTransferFrom(address,address,uint256)"](addr1, addr2, 1);

아래와 같이 사용하여 준다.

await expect(exampleERC721.connect(addr2).transferFrom(addr1.address,addr2.address,tokenId))
.to.emit(exampleERC721, "Transfer")
.withArgs(addr1.address,addr2.address,tokenId)



 await expect(exampleERC721.connect(addr1)["safeTransferFrom(address,address,uint256)"](addr2.address,addr1.address,tokenId))
.to.emit(exampleERC721, "Transfer")
.withArgs(addr2.address,addr1.address,tokenId)

위와 아래를 비교해보면 그 차이를 알 수 있다.

이렇게 오버라이딩된 것을 고려하여 테스트코드 까지 작성하면 토큰을 받아와서 금고처럼 사용하거나 플랫폼 처럼 사용하는 컨트렉트에서 safeTransferFrom에 대비할 수 있다.

 

 

728x90
반응형
Comments