
WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
WTF Academy: wtf.academy

WTF Solidity 合约安全: S08. 绕过合约检查
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍绕过合约长度检查,并介绍预防的方法。绕过合约检查很多 freemint 的项目为了限制科学家(程序员)会用到 isContract() 方法,希望将调用者 msg.sender 限制为外部账户(EOA),而非合约。这个函数利用 extcodesize 获取该地址所存储的 bytecode 长度(runtime),若大于0,则判断为合约,否则就是EOA(用户)。 // 利用 extcodesize 检查是否为合约 function isContract(address account) public view returns (bool) { // extcodesize > 0 的地址一定是合约...

WTF Solidity 合约安全: S09. 拒绝服务
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的拒绝服务(Denial of Service, DoS)漏洞,并介绍预防的方法。NFT项目 Akutar 曾因为 DoS 漏洞损失 11,539 ETH,当时价值 3400 万美元。DoS在 Web2 中,拒绝服务攻击(DoS)是指通过向服务器发送大量垃圾信息或干扰信息的方式,导致服务器无法向正常用户提供服务的现象。而在 Web3,它指的是利用漏洞使得智能合约无法正常提供服务。 在2022年4月,一个很火的 NFT 项目名为 Akutar,他们使用荷兰拍卖进行公开发行,筹集了 11,539.5 ETH,非常成功。之前持有他们社区Pass的参与者会得到 0.5 ETH的退款,但是他们处理...

WTF Solidity 合约安全 S06. 签名重放
我最近在重新学solidity,巩固一下细节,也写一个“WTF Solidity极简入门”,供小白们使用(编程大佬可以另找教程),每周更新1-3讲。 推特:@0xAA_Science|@WTFAcademy_ 社区:Discord|微信群|官网 wtf.academy 所有代码和教程开源在github: github.com/AmazingAng/WTFSolidity这一讲,我们将介绍智能合约的签名重放(Signature Replay)攻击和预防方法,它曾间接导致了著名做市商 Wintermute 被盗2000万枚 $OP。签名重放上学的时候,老师经常会让家长签字,有时候家长很忙,我就会很“贴心”照着以前的签字抄一遍。某种意义上来说,这就是签名重放。 在区块链中,数字签名可以用于识别数据签名者和验证数据完整性。发送交易时,用户使用私钥签名交易,使得其他人可以验证交易是由相应账户发出的。智能合约也能利用 ECDSA 算法验证用户将在链下创建的签名,然后执行铸造或转账等逻辑。更多关于数字签名的介绍请见WTF Solidity第37讲:数字签名。 数字签名一般有两种常见的重放攻击...
WTF Academy: wtf.academy

Subscribe to 0xAA

Subscribe to 0xAA
Share Dialog
Share Dialog
>100 subscribers
>100 subscribers


我最近在重新学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;
_name和_symbol是两个string,存储代币的名称和代号。
_owners是tokenId到owner地址的Mapping,存储每个代币的持有人。
_balances是owner地址到持币数量的Mapping,存储每个地址的持仓量。
_tokenApprovals是tokenId到授权地址的Mapping,存储每个token的授权信息。
_operatorApprovals是owner地址到是否批量批准的Mapping,存储每个owner的批量授权信息。注意,批量授权会把你钱包持有这个系列的所有nft都授权给另一个地址,别人可以随意支配。
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合约上做了什么改动。
我最近在重新学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;
_name和_symbol是两个string,存储代币的名称和代号。
_owners是tokenId到owner地址的Mapping,存储每个代币的持有人。
_balances是owner地址到持币数量的Mapping,存储每个地址的持仓量。
_tokenApprovals是tokenId到授权地址的Mapping,存储每个token的授权信息。
_operatorApprovals是owner地址到是否批量批准的Mapping,存储每个owner的批量授权信息。注意,批量授权会把你钱包持有这个系列的所有nft都授权给另一个地址,别人可以随意支配。
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合约上做了什么改动。
No activity yet