我最近在重新学solidity,巩固一下细节,也写一个“Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。
欢迎关注我的推特:@0xAA_Science
WTF技术社群discord,内有加微信群方法:链接
所有代码和教程开源在github(1024个star发课程认证,2048个star发社群NFT): github.com/AmazingAng/WTFSolidity
不知不觉我已经完成了Solidity极简教程的前13讲(基础),内容包括:Helloworld.sol,变量类型,存储位置,函数,控制流,构造函数,修饰器,事件,继承,抽象合约,接口,库,异常。在进阶内容之前,我决定做一个ERC721
的专题,把之前的内容综合运用,帮助大家更好的复习基础知识,并且更深刻的理解ERC721
合约。希望在学习完这个专题之后,每个人都能发行自己的NFT
。
我在ERC721前两讲介绍了它的相关库和接口,终于这讲可以介绍主合约了。ERC721主合约包含6个状态变量和28个函数,我们将会一一介绍。并且,我给ERC721代码增加了中文注释,方便大家使用。
// 代币名称
string private _name;
// 代币代号
string private _symbol;
// tokenId到owner地址Mapping
mapping(uint256 => address) private _owners;
// owner地址到持币数量Mapping
mapping(address => uint256) private _balances;
// tokenId到授权地址Mapping
mapping(uint256 => address) private _tokenApprovals;
// owner地址到是否批量批准Mapping
mapping(address => mapping(address => bool)) private _operatorApprovals;
constructor
:构造函数,设定ERC721
代币的名字和代号(_name
和_symbol
变量)。 constructor(string memory name_, string memory symbol_) {
_name = name_;
_symbol = symbol_;
}
supportsInterface
:实现IERC165
接口supportsInterface
,详见ERC721专题第二讲 function supportsInterface(bytes4 interfaceId) public view virtual override(ERC165, IERC165) returns (bool) {
return
interfaceId == type(IERC721).interfaceId ||
interfaceId == type(IERC721Metadata).interfaceId ||
super.supportsInterface(interfaceId);
}
balanceOf
:实现IERC721
的balanceOf
,利用_balances
变量查询owner
地址的balance
。 function balanceOf(address owner) public view virtual override returns (uint256) {
require(owner != address(0), "ERC721: balance query for the zero address");
return _balances[owner];
}
ownerOf
:实现IERC721
的ownerOf
,利用_owners
变量查询tokenId
的owner
。 function ownerOf(uint256 tokenId) public view virtual override returns (address) {
address owner = _owners[tokenId];
require(owner != address(0), "ERC721: owner query for nonexistent token");
return owner;
}
name
:实现IERC721Metadata
的name
,查询代币名称。 function name() public view virtual override returns (string memory) {
return _name;
}
symbol
:实现IERC721Metadata
的symbol
,查询代币代号。 function symbol() public view virtual override returns (string memory) {
return _symbol;
}
tokenURI
:实现IERC721Metadata
的tokenURI
,查询代币metadata
存放的网址。Opensea还有小狐狸钱包显示你NFT
的图片,调用的就是这个函数。 function tokenURI(uint256 tokenId) public view virtual override returns (string memory) {
require(_exists(tokenId), "ERC721Metadata: URI query for nonexistent token");
string memory baseURI = _baseURI();
return bytes(baseURI).length > 0 ? string(abi.encodePacked(baseURI, tokenId.toString())) : "";
}
_baseURI
:基URI
,会被tokenURI()
调用,跟tokenId
拼成tokenURI
,默认为空,需要子合约重写这个函数。 function _baseURI() internal view virtual returns (string memory) {
return "";
}
approve
:实现IERC721
的approve
,将tokenId
授权给 to
地址。条件:to
不是owner
,且msg.sender
是owner
或授权地址。调用_approve
函数。 function approve(address to, uint256 tokenId) public virtual override {
address owner = ERC721.ownerOf(tokenId);
require(to != owner, "ERC721: approval to current owner");
require(
_msgSender() == owner || isApprovedForAll(owner, _msgSender()),
"ERC721: approve caller is not owner nor approved for all"
);
_approve(to, tokenId);
}
getApproved
:实现IERC721
的getApproved
,利用_tokenApprovals
变量查询tokenId
的授权地址。 function getApproved(uint256 tokenId) public view virtual override returns (address) {
require(_exists(tokenId), "ERC721: approved query for nonexistent token");
return _tokenApprovals[tokenId];
}
setApprovalForAll
:实现IERC721
的setApprovalForAll
,将持有代币全部授权给operator
地址。调用_setApprovalForAll
函数。 function setApprovalForAll(address operator, bool approved) public virtual override {
_setApprovalForAll(_msgSender(), operator, approved);
}
isApprovedForAll
:实现IERC721
的isApprovedForAll
,利用_operatorApprovals
变量查询owner
地址是否将所持NFT批量授权给了operator
地址。 function isApprovedForAll(address owner, address operator) public view virtual override returns (bool) {
return _operatorApprovals[owner][operator];
}
transferFrom
:实现IERC721
的transferFrom
,非安全转账,不建议使用。调用_transfer
函数。 function transferFrom(
address from,
address to,
uint256 tokenId
) public virtual override {
//solhint-disable-next-line max-line-length
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_transfer(from, to, tokenId);
}
safeTransferFrom
:实现IERC721
的safeTransferFrom
,安全转账,调用了_safeTransfer
函数。 function safeTransferFrom(
address from,
address to,
uint256 tokenId,
bytes memory _data
) public virtual override {
require(_isApprovedOrOwner(_msgSender(), tokenId), "ERC721: transfer caller is not owner nor approved");
_safeTransfer(from, to, tokenId, _data);
}
_safeTransfer
:安全转账,安全地将 tokenId
代币从 from
转移到 to
,会检查合约接收者是否了解 ERC721 协议,以防止代币被永久锁定。调用了_transfer
函数和_checkOnERC721Received
函数。条件:
from
不能是0地址.to
不能是0地址.tokenId
代币必须存在,并且被 from
拥有.to
是智能合约, 他必须支持 IERC721Receiver-onERC721Received
. function _safeTransfer(
address from,
address to,
uint256 tokenId,
bytes memory _data
) internal virtual {
_transfer(from, to, tokenId);
require(_checkOnERC721Received(from, to, tokenId, _data), "ERC721: transfer to non ERC721Receiver implementer");
}
_exists
:查询 tokenId
是否存在(等价于查询他的owner
是否为非0地址)。 function _exists(uint256 tokenId) internal view virtual returns (bool) {
return _owners[tokenId] != address(0);
}
_isApprovedOrOwner
:查询 spender
地址是否被可以使用tokenId
(他是owner
或被授权地址)。 function _isApprovedOrOwner(address spender, uint256 tokenId) internal view virtual returns (bool) {
require(_exists(tokenId), "ERC721: operator query for nonexistent token");
address owner = ERC721.ownerOf(tokenId);
return (spender == owner || getApproved(tokenId) == spender || isApprovedForAll(owner, spender));
}
_safeMint
:安全mint
函数,铸造tokenId
并转账给 to
地址。 条件:
tokenId
尚不存在,to
为智能合约,他要支持IERC721Receiver-onERC721Received
接口。 function _safeMint(address to, uint256 tokenId) internal virtual {
_safeMint(to, tokenId, "");
}
_safeMint
的实现,调用了_checkOnERC721Received
函数和_mint
函数。 function _safeMint(
address to,
uint256 tokenId,
bytes memory _data
) internal virtual {
_mint(to, tokenId);
require(
_checkOnERC721Received(address(0), to, tokenId, _data),
"ERC721: transfer to non ERC721Receiver implementer"
);
}
_mint
:internal
铸造函数。通过调整_balances
和_owners
变量来铸造tokenId
并转账给 to
,同时释放Tranfer
事件。条件:
tokenId
尚不存在。to
不是0地址. function _mint(address to, uint256 tokenId) internal virtual {
require(to != address(0), "ERC721: mint to the zero address");
require(!_exists(tokenId), "ERC721: token already minted");
_beforeTokenTransfer(address(0), to, tokenId);
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(address(0), to, tokenId);
_afterTokenTransfer(address(0), to, tokenId);
}
_burn
:internal
销毁函数,通过调整_balances
和_owners
变量来销毁tokenId
,同时释放Tranfer
事件。条件:tokenId
存在。 function _burn(uint256 tokenId) internal virtual {
address owner = ERC721.ownerOf(tokenId);
_beforeTokenTransfer(owner, address(0), tokenId);
// 清空授权
_approve(address(0), tokenId);
_balances[owner] -= 1;
delete _owners[tokenId];
emit Transfer(owner, address(0), tokenId);
_afterTokenTransfer(owner, address(0), tokenId);
}
_transfer
:转账函数。通过调整_balances
和_owner
变量将 tokenId
从 from
转账给 to
,同时释放Tranfer
事件。条件:
tokenId
被 from
拥有to
不是0地址 function _transfer(
address from,
address to,
uint256 tokenId
) internal virtual {
require(ERC721.ownerOf(tokenId) == from, "ERC721: transfer from incorrect owner");
require(to != address(0), "ERC721: transfer to the zero address");
_beforeTokenTransfer(from, to, tokenId);
// 清空授权
_approve(address(0), tokenId);
_balances[from] -= 1;
_balances[to] += 1;
_owners[tokenId] = to;
emit Transfer(from, to, tokenId);
_afterTokenTransfer(from, to, tokenId);
}
_approve
:授权函数。通过调整_tokenApprovals
来,授权 to
地址操作 tokenId
,同时释放Approval
事件。 function _approve(address to, uint256 tokenId) internal virtual {
_tokenApprovals[tokenId] = to;
emit Approval(ERC721.ownerOf(tokenId), to, tokenId);
}
_setApprovalForAll
:批量授权函数。通过调整_operatorApprovals
变量,批量授权 to
来操作 owner
全部代币,同时释放ApprovalForAll
事件。 function _setApprovalForAll(
address owner,
address operator,
bool approved
) internal virtual {
require(owner != operator, "ERC721: approve to caller");
_operatorApprovals[owner][operator] = approved;
emit ApprovalForAll(owner, operator, approved);
}
_checkOnERC721Received
:函数,用于在 to
为合约的时候调用IERC721Receiver-onERC721Received
, 以防 tokenId
被不小心转入黑洞。 function _checkOnERC721Received(
address from,
address to,
uint256 tokenId,
bytes memory _data
) private returns (bool) {
if (to.isContract()) {
try IERC721Receiver(to).onERC721Received(_msgSender(), 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 {
assembly {
revert(add(32, reason), mload(reason))
}
}
}
} else {
return true;
}
}
_beforeTokenTransfer
:这个函数在转账之前会被调用(包括mint
和burn
)。默认为空,子合约可以选择重写。 function _beforeTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
_afterTokenTransfer
:这个函数在转账之后会被调用(包括mint
和burn
)。默认为空,子合约可以选择重写。 function _afterTokenTransfer(
address from,
address to,
uint256 tokenId
) internal virtual {}
本文是ERC721
专题的第三讲,我介绍了ERC721
主合约的全部变量和函数,并给出了合约的中文注释。有了ERC721
标准,NFT项目方只需要把mint
函数包装一下,就可以发行NFT了。下一讲,我们会介绍无聊猿BAYC
的合约,了解一下最火NFT
在标准ERC721
合约上做了什么改动。