#鐵人賽 #ethereum #solidity
本日課程將實作 ERC721 的安全轉移用函式。
本日影片: https://youtu.be/eBngeJk0vqA
本日合約:
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.17;
interface IERC721Receiver {
function onERC721Received(
address operator,
address from,
uint256 tokenId,
bytes calldata data
) external returns (bytes4);
}
interface IERC721 {
// Event
event Transfer(address indexed from, address indexed to, uint256 indexed tokenId);
event Approval(address indexed owner, address indexed approved, uint256 indexed tokenId);
event ApprovalForAll(address indexed owner, address indexed operator, bool approved);
// Query
function balanceOf(address owner) external view returns (uint256 balance);
function ownerOf(uint256 tokenId) external view returns (address owner);
// Transfer
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) external;
function safeTransferFrom(address from, address to, uint256 tokenId) external;
function transferFrom(address from, address to, uint256 tokenId) external;
// Approve
function approve(address to, uint256 tokenId) external;
function setApprovalForAll(address operator, bool _approved) external;
function getApproved(uint256 tokenId) external view returns (address operator);
function isApprovedForAll(address owner, address operator) external view returns (bool);
}
contract ERC721 is IERC721 {
mapping(address => uint256) _balances;
mapping(uint256 => address) _owners;
mapping(uint256 => address) _tokenApprovals;
mapping(address => mapping(address => bool)) _operatorApprovals;
function balanceOf(address owner) public view returns (uint256) {
require(owner != address(0), "ERROR: address 0 cannot be owner");
return _balances[owner];
}
function ownerOf(uint256 tokenId) public view returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ERROR: tokenId is not valid Id");
return owner;
}
function approve(address to, uint256 tokenId) public {
address owner = _owners[tokenId];
require(owner != to, "ERROR: owner == to");
require(owner == msg.sender || isApprovedForAll(owner, msg.sender), "ERROR: Caller is not token owner / approved for all");
_tokenApprovals[tokenId] = to;
emit Approval(owner, to, tokenId);
}
function getApproved(uint256 tokenId) public view returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ERROR: Token is not minted or is burn");
return _tokenApprovals[tokenId];
}
function setApprovalForAll(address operator, bool _approved) public {
require(msg.sender != operator, "ERROR: owner == operator");
_operatorApprovals[msg.sender][operator] = _approved;
emit ApprovalForAll(msg.sender, operator, _approved);
}
function isApprovedForAll(address owner, address operator) public view returns (bool) {
return _operatorApprovals[owner][operator];
}
function transferFrom(address from, address to, uint256 tokenId) public {
_transfer(from, to, tokenId);
}
function _transfer(address from, address to, uint256 tokenId) internal {
address owner = _owners[tokenId];
require(owner == from, "ERROR: Owner is not the from address");
require(msg.sender == owner || isApprovedForAll(owner, msg.sender) || getApproved(tokenId) == msg.sender, "ERROR: Caller doesn't have permission to transfer");
delete _tokenApprovals[tokenId];
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
}
function safeTransferFrom(address from, address to, uint256 tokenId, bytes calldata data) public {
_safeTransfer(from, to, tokenId, data);
}
function safeTransferFrom(address from, address to, uint256 tokenId) public {
_safeTransfer(from, to, tokenId, "");
}
function _safeTransfer(address from, address to, uint256 tokenId, bytes memory data) internal {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, data), "ERROR: ERC721Receiver is not implmeneted");
}
// Reference Link: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol#L429-L451
function _checkOnERC721Received(address from, address to, uint256 tokenId, bytes memory data) private returns (bool) {
if (to.code.length > 0 /* to is a contract*/) {
try IERC721Receiver(to).onERC721Received(msg.sender, from, tokenId, data) returns (bytes4 retval) {
return retval == IERC721Receiver.onERC721Received.selector;
} catch (bytes memory reason) {
if (reason.length == 0) {
revert("ERC721: transfer to non ERC721Receiver implementer");
} else {
/// @solidity memory-safe-assembly
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
}
本影片提到的連結:
參考實作連結 1: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/IERC721Receiver.sol
參考實作連結 2: https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC721/ERC721.sol
「Remix IDE」: https://remix.ethereum.org/
「在 2022 年,我們該如何寫智能合約」: https://ithelp.ithome.com.tw/users/20083367/ironman/5019
「那些關於 Ethereum 的事」: https://ithelp.ithome.com.tw/users/20083367/ironman/5136
「一本關於 Ethereum 與 Solidity 智能合約的書」: https://solidity.tw
「文章或主題許願池」: https://github.com/hydai/solidity-book/issues
「本系列播放清單」: https://www.youtube.com/playlist?list=PLHmOMPRfmOxQYDnXAc1hKY6ra4WDU8ZlM