搭建BSC Fullnode
要运行BSC全节点,首先需要有服务器,币安官方推荐服务器配置VPS running recent versions of Mac OS X or Linux.IMPORTANT 2T GB of free disk space, solid-state drive(SSD), gp3, 8k IOPS, 250MB/S throughput, read latency <1ms. (if start with snap/fast sync, it will need NVMe SSD)16 cores of CPU and 64 gigabytes of memory (RAM).Suggest m5zn.3xlarge instance type on AWS, c2-standard-16 on Google cloud.A broadband Internet connection with upload/download speeds of 5 megabyte per second我是在aliyun买的VPS,配置是16核64G,系统盘40G,数据盘3000G ESSD...
以Synthetix为例,MEV策略剖析
本文翻译自Robert Miller的Anatomy of an MEV Strategy: Synthetix 几个月前臭名昭著的alpha泄露者KALEB在Flashbots公开的searchers频道发表了下列消息KALBE泄露了关于Synthetix变动的数千万美金的alpha消息。在这个机器人运营商的小房间里分享alpha就像丢给狮子一块红肉一样,在快速看了合约之后可以确认有笔另人晕眩的钱处在危机中。 在接下来的几周,我计划并且尝试去执行策略来捕获KALEB分享的MEV。我会开源我用的代码并一步一步展示整个过程和策略。你将不能运行我的代码去赚钱,但是这篇文章将会教你我是如何设计这个新的搜索者并会包含许多alpha。很自然的,这将会有点技术性,但我会尽量让本文对于非技术读者来说好理解。第一步,识别机会我不是一个Synthetix专家,因此第一步是去学习我将要涉及的操作。具体如下:我找出了相关合约我在Synthetix博客里读了它们的高级别功能并且搜索了相关文档我确保理解了将要实行的治理变动我查找了相关函数总结一下这阶段的工作,Synthetix已经试验了以ETH为抵押去铸...
Bitmap结构在ENSToken里的应用
在ENSToken的合约里看到了Bitmaps的应用,在地址认领空投时用了Merkle树证明来check用户地址和认领数量,进而会对应一个Merkle的index,为了防止重复认领空投,合约里用了OpenZeppelin的Bitmaps库来做位图存储,地址认领成功后,就将对应的index在位图里存true,下次如果再来认领就会判断这个位图,如果为true时就返回错误,以此来防止重复认领空投。BitMaps.BitMap private claimed; /** * @dev Claims airdropped tokens. * @param amount The amount of the claim being made. * @param delegate The address the tokenholder wants to delegate their votes to. * @param merkleProof A merkle proof proving the claim is valid. */ function claimTokens(uint256 amo...
<100 subscribers
搭建BSC Fullnode
要运行BSC全节点,首先需要有服务器,币安官方推荐服务器配置VPS running recent versions of Mac OS X or Linux.IMPORTANT 2T GB of free disk space, solid-state drive(SSD), gp3, 8k IOPS, 250MB/S throughput, read latency <1ms. (if start with snap/fast sync, it will need NVMe SSD)16 cores of CPU and 64 gigabytes of memory (RAM).Suggest m5zn.3xlarge instance type on AWS, c2-standard-16 on Google cloud.A broadband Internet connection with upload/download speeds of 5 megabyte per second我是在aliyun买的VPS,配置是16核64G,系统盘40G,数据盘3000G ESSD...
以Synthetix为例,MEV策略剖析
本文翻译自Robert Miller的Anatomy of an MEV Strategy: Synthetix 几个月前臭名昭著的alpha泄露者KALEB在Flashbots公开的searchers频道发表了下列消息KALBE泄露了关于Synthetix变动的数千万美金的alpha消息。在这个机器人运营商的小房间里分享alpha就像丢给狮子一块红肉一样,在快速看了合约之后可以确认有笔另人晕眩的钱处在危机中。 在接下来的几周,我计划并且尝试去执行策略来捕获KALEB分享的MEV。我会开源我用的代码并一步一步展示整个过程和策略。你将不能运行我的代码去赚钱,但是这篇文章将会教你我是如何设计这个新的搜索者并会包含许多alpha。很自然的,这将会有点技术性,但我会尽量让本文对于非技术读者来说好理解。第一步,识别机会我不是一个Synthetix专家,因此第一步是去学习我将要涉及的操作。具体如下:我找出了相关合约我在Synthetix博客里读了它们的高级别功能并且搜索了相关文档我确保理解了将要实行的治理变动我查找了相关函数总结一下这阶段的工作,Synthetix已经试验了以ETH为抵押去铸...
Bitmap结构在ENSToken里的应用
在ENSToken的合约里看到了Bitmaps的应用,在地址认领空投时用了Merkle树证明来check用户地址和认领数量,进而会对应一个Merkle的index,为了防止重复认领空投,合约里用了OpenZeppelin的Bitmaps库来做位图存储,地址认领成功后,就将对应的index在位图里存true,下次如果再来认领就会判断这个位图,如果为true时就返回错误,以此来防止重复认领空投。BitMaps.BitMap private claimed; /** * @dev Claims airdropped tokens. * @param amount The amount of the claim being made. * @param delegate The address the tokenholder wants to delegate their votes to. * @param merkleProof A merkle proof proving the claim is valid. */ function claimTokens(uint256 amo...
Share Dialog
Share Dialog
NFT白单和公售mint时,有些项目允许一个tx交易mint多个NFT,以太坊标准的ERC721在mint多个时gas成本成倍增加,对用户来说不太友好。Azuki对此进行了优化,发布了ERC721A合约,这个合约也在跟随着最新市场变化而不断优化,现对当前最新版本v3.0.0进行解读。
合约源码见
https://github.com/chiru-labs/ERC721A
核心优化是不再每个tokenId都存储对应的所有者地址,对于mint多个的地址只在第一个tokenId上存储所有者信息,但这样依赖于tokenId的连续性。也就是说mint多个时,tokenId是连续的,对于某些项目的tokenId不是连续的来说就不太适用。比如meebits的tokenId是随机去除获取的。
下面对关键代码进行了中文的补充注释
// Compiler will pack this into a single 256bit word.
struct TokenOwnership {
// The address of the owner.
address addr;//所有者地址 160位
// Keeps track of the start time of ownership with minimal overhead for tokenomics.
uint64 startTimestamp;//连续片段的第一个token归属给用户的时间戳
// Whether the token has been burned.
bool burned;//是否燃烧 8位
}//160+64+8=212,对齐到264位
// Compiler will pack this into a single 256bit word.
struct AddressData {
// Realistically, 2**64-1 is more than enough.
uint64 balance;//token余额
// Keeps track of mint count with minimal overhead for tokenomics.
uint64 numberMinted;//mint数量
// Keeps track of burn count with minimal overhead for tokenomics.
uint64 numberBurned;//燃烧数量
// For miscellaneous variable(s) pertaining to the address
// (e.g. number of whitelist mint slots used).
// If there are multiple variables, please pack them into a uint64.
uint64 aux;//附加参数
}
// The tokenId of the next token to be minted.
uint256 internal _currentIndex;
// The number of tokens burned.
uint256 internal _burnCounter;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to ownership details
// An empty struct value does not necessarily mean the token is unowned. See ownershipOf implementation for details.
mapping(uint256 => TokenOwnership) internal _ownerships;//tokenId与所有者地址的映射关系
// Mapping owner address to address data
mapping(address => AddressData) private _addressData;//地址与地址token的映射关系
主要用了TokenOwnership来存储所有者信息,AddressData存储所有者对应的token信息
/**
* Gas spent here starts off proportional to the maximum mint batch size.
* It gradually moves to O(1) as tokens get transferred around in the collection over time.
*/
function ownershipOf(uint256 tokenId) internal view returns (TokenOwnership memory) {
uint256 curr = tokenId;
unchecked {
if (_startTokenId() <= curr && curr < _currentIndex) {//tokenId在当前mint的数量范围内
TokenOwnership memory ownership = _ownerships[curr];
if (!ownership.burned) {//未燃烧
if (ownership.addr != address(0)) {
return ownership;
}
// Invariant:
// There will always be an ownership that has an address and is not burned
// before an ownership that does not have an address and is not burned.
// Hence, curr will not underflow.
while (true) {//tokenId对应的所有者地址是空,表示此tokenId在存储时不是连续块的第一个token,因此要向前遍历直接找到非0地址
curr--;
ownership = _ownerships[curr];
if (ownership.addr != address(0)) {
return ownership;
}
}
}
}
}
revert OwnerQueryForNonexistentToken();
}
因为不再每个tokenId都存储所有者信息,所以在根据tokenId查找所有者时,有可能要遍历查找,但读交易是不需要gas的
多个mint时,只存储了第一个tokenId对应的_addressData和_ownerships,这样可以省去重复的gas消耗;transfer时如果不是第一个tokenId的transfer,需要更新tokenId+1的_ownerships;burn时与transfer类似。
项目官方有对mint的gas消耗与ERC721进行了对比,可以说在gas消耗上节省了许多,往后应该会有越来越多的NFT项目采用此合约标准。

NFT白单和公售mint时,有些项目允许一个tx交易mint多个NFT,以太坊标准的ERC721在mint多个时gas成本成倍增加,对用户来说不太友好。Azuki对此进行了优化,发布了ERC721A合约,这个合约也在跟随着最新市场变化而不断优化,现对当前最新版本v3.0.0进行解读。
合约源码见
https://github.com/chiru-labs/ERC721A
核心优化是不再每个tokenId都存储对应的所有者地址,对于mint多个的地址只在第一个tokenId上存储所有者信息,但这样依赖于tokenId的连续性。也就是说mint多个时,tokenId是连续的,对于某些项目的tokenId不是连续的来说就不太适用。比如meebits的tokenId是随机去除获取的。
下面对关键代码进行了中文的补充注释
// Compiler will pack this into a single 256bit word.
struct TokenOwnership {
// The address of the owner.
address addr;//所有者地址 160位
// Keeps track of the start time of ownership with minimal overhead for tokenomics.
uint64 startTimestamp;//连续片段的第一个token归属给用户的时间戳
// Whether the token has been burned.
bool burned;//是否燃烧 8位
}//160+64+8=212,对齐到264位
// Compiler will pack this into a single 256bit word.
struct AddressData {
// Realistically, 2**64-1 is more than enough.
uint64 balance;//token余额
// Keeps track of mint count with minimal overhead for tokenomics.
uint64 numberMinted;//mint数量
// Keeps track of burn count with minimal overhead for tokenomics.
uint64 numberBurned;//燃烧数量
// For miscellaneous variable(s) pertaining to the address
// (e.g. number of whitelist mint slots used).
// If there are multiple variables, please pack them into a uint64.
uint64 aux;//附加参数
}
// The tokenId of the next token to be minted.
uint256 internal _currentIndex;
// The number of tokens burned.
uint256 internal _burnCounter;
// Token name
string private _name;
// Token symbol
string private _symbol;
// Mapping from token ID to ownership details
// An empty struct value does not necessarily mean the token is unowned. See ownershipOf implementation for details.
mapping(uint256 => TokenOwnership) internal _ownerships;//tokenId与所有者地址的映射关系
// Mapping owner address to address data
mapping(address => AddressData) private _addressData;//地址与地址token的映射关系
主要用了TokenOwnership来存储所有者信息,AddressData存储所有者对应的token信息
/**
* Gas spent here starts off proportional to the maximum mint batch size.
* It gradually moves to O(1) as tokens get transferred around in the collection over time.
*/
function ownershipOf(uint256 tokenId) internal view returns (TokenOwnership memory) {
uint256 curr = tokenId;
unchecked {
if (_startTokenId() <= curr && curr < _currentIndex) {//tokenId在当前mint的数量范围内
TokenOwnership memory ownership = _ownerships[curr];
if (!ownership.burned) {//未燃烧
if (ownership.addr != address(0)) {
return ownership;
}
// Invariant:
// There will always be an ownership that has an address and is not burned
// before an ownership that does not have an address and is not burned.
// Hence, curr will not underflow.
while (true) {//tokenId对应的所有者地址是空,表示此tokenId在存储时不是连续块的第一个token,因此要向前遍历直接找到非0地址
curr--;
ownership = _ownerships[curr];
if (ownership.addr != address(0)) {
return ownership;
}
}
}
}
}
revert OwnerQueryForNonexistentToken();
}
因为不再每个tokenId都存储所有者信息,所以在根据tokenId查找所有者时,有可能要遍历查找,但读交易是不需要gas的
多个mint时,只存储了第一个tokenId对应的_addressData和_ownerships,这样可以省去重复的gas消耗;transfer时如果不是第一个tokenId的transfer,需要更新tokenId+1的_ownerships;burn时与transfer类似。
项目官方有对mint的gas消耗与ERC721进行了对比,可以说在gas消耗上节省了许多,往后应该会有越来越多的NFT项目采用此合约标准。

/**
* @dev Mints `quantity` tokens and transfers them to `to`.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `quantity` must be greater than 0.
*
* Emits a {Transfer} event.
*/
function _mint(
address to,
uint256 quantity,
bytes memory _data,
bool safe
) internal {
uint256 startTokenId = _currentIndex;//当前tokenId索引
if (to == address(0)) revert MintToZeroAddress();//不能mint到0地址
if (quantity == 0) revert MintZeroQuantity();//数量不能=0
_beforeTokenTransfers(address(0), to, startTokenId, quantity);
// Overflows are incredibly unrealistic.
// balance or numberMinted overflow if current value of either + quantity > 1.8e19 (2**64) - 1
// updatedIndex overflows if _currentIndex + quantity > 1.2e77 (2**256) - 1
unchecked {
_addressData[to].balance += uint64(quantity);//目标地址token余额+1
_addressData[to].numberMinted += uint64(quantity);//目标地址mint数量+1
_ownerships[startTokenId].addr = to;//设置tokenId所有者
_ownerships[startTokenId].startTimestamp = uint64(block.timestamp);//设置连续片段的第一个token归属给用户的时间戳
uint256 updatedIndex = startTokenId;
uint256 end = updatedIndex + quantity;//最后一个tokenId
if (safe && to.isContract()) {//目标地址是合约
do {
emit Transfer(address(0), to, updatedIndex);//事件日志
if (!_checkContractOnERC721Received(address(0), to, updatedIndex++, _data)) {//校验是否符合IERC721Receiversf标准
revert TransferToNonERC721ReceiverImplementer();
}
} while (updatedIndex != end);
// Reentrancy protection
if (_currentIndex != startTokenId) revert();
} else {
do {
emit Transfer(address(0), to, updatedIndex++);//事件日志
} while (updatedIndex != end);
}
_currentIndex = updatedIndex;//更新当前tokenId索引,供下次mint使用
}
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(
address from,
address to,
uint256 tokenId
) private {
TokenOwnership memory prevOwnership = ownershipOf(tokenId);//获取tokenId所有者地址
bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr ||
isApprovedForAll(prevOwnership.addr, _msgSender()) ||
getApproved(tokenId) == _msgSender());//tokenId是函数调用方自己的 或者 已授权给函数调用方
if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved();
if (prevOwnership.addr != from) revert TransferFromIncorrectOwner();//tokenId所有者与from参数要一样
if (to == address(0)) revert TransferToZeroAddress();//不能转到0地址
_beforeTokenTransfers(from, to, tokenId, 1);
// Clear approvals from the previous owner
_approve(address(0), tokenId, prevOwnership.addr);//转出地址的tokenId授权记录要清空
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as tokenId would have to be 2**256.
unchecked {
_addressData[from].balance -= 1;//转出地址余额-1
_addressData[to].balance += 1;//转入地址余额+1
_ownerships[tokenId].addr = to;//更新tokenId所有者地址为目标地址
_ownerships[tokenId].startTimestamp = uint64(block.timestamp);//更新连续片段的第一个token归属给用户的时间戳
// If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it.
// Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls.
uint256 nextTokenId = tokenId + 1;
if (_ownerships[nextTokenId].addr == address(0)) {//下一个tokenId的所有者信息是空时,表示下一个tokenId也是当前tokenId拥有的,因此要更新所有者信息
// This will suffice for checking _exists(nextTokenId),
// as a burned slot cannot contain the zero address.
if (nextTokenId < _currentIndex) {
_ownerships[nextTokenId].addr = prevOwnership.addr;//更新下一个tokenId所有者地址为转出地址
_ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp;//更新连续片段的第一个token归属给用户的时间戳
}
}
}
emit Transfer(from, to, tokenId);
_afterTokenTransfers(from, to, tokenId, 1);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
TokenOwnership memory prevOwnership = ownershipOf(tokenId);//获取tokenId所有者地址
_beforeTokenTransfers(prevOwnership.addr, address(0), tokenId, 1);
// Clear approvals from the previous owner
_approve(address(0), tokenId, prevOwnership.addr);//地址的tokenId授权记录要清空
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as tokenId would have to be 2**256.
unchecked {
_addressData[prevOwnership.addr].balance -= 1;//地址余额-1
_addressData[prevOwnership.addr].numberBurned += 1;//地址煅烧数量+1
// Keep track of who burned the token, and the timestamp of burning.
_ownerships[tokenId].addr = prevOwnership.addr;//更新tokenid的所有者地址,因为可能tokenId是属于连续块中的非第一个,之前mint时没有做所有者记录
_ownerships[tokenId].startTimestamp = uint64(block.timestamp);//更新连续片段的第一个token归属给用户的时间戳
_ownerships[tokenId].burned = true;//更新tokenId状态为已燃烧
// If the ownership slot of tokenId+1 is not explicitly set, that means the burn initiator owns it.
// Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls.
uint256 nextTokenId = tokenId + 1;
if (_ownerships[nextTokenId].addr == address(0)) {//下一个tokenId的所有者信息是空时,表示下一个tokenId也是当前tokenId拥有的,因此要更新所有者信息
// This will suffice for checking _exists(nextTokenId),
// as a burned slot cannot contain the zero address.
if (nextTokenId < _currentIndex) {
_ownerships[nextTokenId].addr = prevOwnership.addr;//更新下一个tokenId所有者地址
_ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp;//更新连续片段的第一个token归属给用户的时间戳
}
}
}
emit Transfer(prevOwnership.addr, address(0), tokenId);
_afterTokenTransfers(prevOwnership.addr, address(0), tokenId, 1);
// Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
unchecked {
_burnCounter++;
}
}
/**
* @dev Mints `quantity` tokens and transfers them to `to`.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `quantity` must be greater than 0.
*
* Emits a {Transfer} event.
*/
function _mint(
address to,
uint256 quantity,
bytes memory _data,
bool safe
) internal {
uint256 startTokenId = _currentIndex;//当前tokenId索引
if (to == address(0)) revert MintToZeroAddress();//不能mint到0地址
if (quantity == 0) revert MintZeroQuantity();//数量不能=0
_beforeTokenTransfers(address(0), to, startTokenId, quantity);
// Overflows are incredibly unrealistic.
// balance or numberMinted overflow if current value of either + quantity > 1.8e19 (2**64) - 1
// updatedIndex overflows if _currentIndex + quantity > 1.2e77 (2**256) - 1
unchecked {
_addressData[to].balance += uint64(quantity);//目标地址token余额+1
_addressData[to].numberMinted += uint64(quantity);//目标地址mint数量+1
_ownerships[startTokenId].addr = to;//设置tokenId所有者
_ownerships[startTokenId].startTimestamp = uint64(block.timestamp);//设置连续片段的第一个token归属给用户的时间戳
uint256 updatedIndex = startTokenId;
uint256 end = updatedIndex + quantity;//最后一个tokenId
if (safe && to.isContract()) {//目标地址是合约
do {
emit Transfer(address(0), to, updatedIndex);//事件日志
if (!_checkContractOnERC721Received(address(0), to, updatedIndex++, _data)) {//校验是否符合IERC721Receiversf标准
revert TransferToNonERC721ReceiverImplementer();
}
} while (updatedIndex != end);
// Reentrancy protection
if (_currentIndex != startTokenId) revert();
} else {
do {
emit Transfer(address(0), to, updatedIndex++);//事件日志
} while (updatedIndex != end);
}
_currentIndex = updatedIndex;//更新当前tokenId索引,供下次mint使用
}
_afterTokenTransfers(address(0), to, startTokenId, quantity);
}
/**
* @dev Transfers `tokenId` from `from` to `to`.
*
* Requirements:
*
* - `to` cannot be the zero address.
* - `tokenId` token must be owned by `from`.
*
* Emits a {Transfer} event.
*/
function _transfer(
address from,
address to,
uint256 tokenId
) private {
TokenOwnership memory prevOwnership = ownershipOf(tokenId);//获取tokenId所有者地址
bool isApprovedOrOwner = (_msgSender() == prevOwnership.addr ||
isApprovedForAll(prevOwnership.addr, _msgSender()) ||
getApproved(tokenId) == _msgSender());//tokenId是函数调用方自己的 或者 已授权给函数调用方
if (!isApprovedOrOwner) revert TransferCallerNotOwnerNorApproved();
if (prevOwnership.addr != from) revert TransferFromIncorrectOwner();//tokenId所有者与from参数要一样
if (to == address(0)) revert TransferToZeroAddress();//不能转到0地址
_beforeTokenTransfers(from, to, tokenId, 1);
// Clear approvals from the previous owner
_approve(address(0), tokenId, prevOwnership.addr);//转出地址的tokenId授权记录要清空
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as tokenId would have to be 2**256.
unchecked {
_addressData[from].balance -= 1;//转出地址余额-1
_addressData[to].balance += 1;//转入地址余额+1
_ownerships[tokenId].addr = to;//更新tokenId所有者地址为目标地址
_ownerships[tokenId].startTimestamp = uint64(block.timestamp);//更新连续片段的第一个token归属给用户的时间戳
// If the ownership slot of tokenId+1 is not explicitly set, that means the transfer initiator owns it.
// Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls.
uint256 nextTokenId = tokenId + 1;
if (_ownerships[nextTokenId].addr == address(0)) {//下一个tokenId的所有者信息是空时,表示下一个tokenId也是当前tokenId拥有的,因此要更新所有者信息
// This will suffice for checking _exists(nextTokenId),
// as a burned slot cannot contain the zero address.
if (nextTokenId < _currentIndex) {
_ownerships[nextTokenId].addr = prevOwnership.addr;//更新下一个tokenId所有者地址为转出地址
_ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp;//更新连续片段的第一个token归属给用户的时间戳
}
}
}
emit Transfer(from, to, tokenId);
_afterTokenTransfers(from, to, tokenId, 1);
}
/**
* @dev Destroys `tokenId`.
* The approval is cleared when the token is burned.
*
* Requirements:
*
* - `tokenId` must exist.
*
* Emits a {Transfer} event.
*/
function _burn(uint256 tokenId) internal virtual {
TokenOwnership memory prevOwnership = ownershipOf(tokenId);//获取tokenId所有者地址
_beforeTokenTransfers(prevOwnership.addr, address(0), tokenId, 1);
// Clear approvals from the previous owner
_approve(address(0), tokenId, prevOwnership.addr);//地址的tokenId授权记录要清空
// Underflow of the sender's balance is impossible because we check for
// ownership above and the recipient's balance can't realistically overflow.
// Counter overflow is incredibly unrealistic as tokenId would have to be 2**256.
unchecked {
_addressData[prevOwnership.addr].balance -= 1;//地址余额-1
_addressData[prevOwnership.addr].numberBurned += 1;//地址煅烧数量+1
// Keep track of who burned the token, and the timestamp of burning.
_ownerships[tokenId].addr = prevOwnership.addr;//更新tokenid的所有者地址,因为可能tokenId是属于连续块中的非第一个,之前mint时没有做所有者记录
_ownerships[tokenId].startTimestamp = uint64(block.timestamp);//更新连续片段的第一个token归属给用户的时间戳
_ownerships[tokenId].burned = true;//更新tokenId状态为已燃烧
// If the ownership slot of tokenId+1 is not explicitly set, that means the burn initiator owns it.
// Set the slot of tokenId+1 explicitly in storage to maintain correctness for ownerOf(tokenId+1) calls.
uint256 nextTokenId = tokenId + 1;
if (_ownerships[nextTokenId].addr == address(0)) {//下一个tokenId的所有者信息是空时,表示下一个tokenId也是当前tokenId拥有的,因此要更新所有者信息
// This will suffice for checking _exists(nextTokenId),
// as a burned slot cannot contain the zero address.
if (nextTokenId < _currentIndex) {
_ownerships[nextTokenId].addr = prevOwnership.addr;//更新下一个tokenId所有者地址
_ownerships[nextTokenId].startTimestamp = prevOwnership.startTimestamp;//更新连续片段的第一个token归属给用户的时间戳
}
}
}
emit Transfer(prevOwnership.addr, address(0), tokenId);
_afterTokenTransfers(prevOwnership.addr, address(0), tokenId, 1);
// Overflow not possible, as _burnCounter cannot be exceed _currentIndex times.
unchecked {
_burnCounter++;
}
}
No comments yet