Web3新手,有无技术背景均可:
研发——可无障碍阅读,理解精美的合约设计
非研发——可能读不懂列举的代码,但能体会标准协议的设计思路
_owners 中用数字映射地址的方式记录每一个 ID 当前对应的所有者,同时也附带用 _balances 记录了当前所有者总计持有的NFT数量mapping(uint256 => address) private _owners;mapping(address => uint256) private _balances;
_owners,从而与ERC20仅 _balances 进行地址与余额的管理,区分出了FT(同质化)与NFT(非同质化)的差别。Mint是如何进行的Mint 意思为铸造,即每个NFT的创造过程,例如之前的爱死机NFT 十四君,公众号:十四君当奈飞的NFT忘记了web2的业务安全
Mint 获取到该NFT的资产证明。Mint 主要是进行了安全判断:判断1:确保转入的不是0x00地址(黑洞地址无法转出,转入则资产损失)
判断2:确保此交易所操作的NFTID是不存在的
操作1:将转入地址的_balances 所持有总数加1
操作2:将对应 NFTID 的所有者修改为转入的地址
操作3:完成交易则发出emit 事件,可以让链下监听到这次交易的数据
/*** @dev Mints `tokenId` and transfers it to `to`.* Emits a {Transfer} event.*/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);}
_beforeTokenTransfer 和 _afterTokenTransfer 属于虚函数,作为标准,是让项目方可以在不修改标准协议的情况下增加一些特定的逻辑代码用的。safeMint更安全safeMint 意为安全的铸造,从代码实现中可以看到他本身也是调用了Mint但是他额外增加了 _checkOnERC721Received 的判断,这点是属于 ERC165 的标准,相当于在完成转入操作后,则判断对方地址,是否是黑洞地址(即无法发起交易NFT操作的地址)是防止转入对象为合约地址时候,其合约没有预设置好转出的函数,导致资产在内无法被转走,从而造成永久损失:/*** @dev Same as {xref-ERC721-_safeMint-address-uint256-}[`_safeMint`], with an additional `data` parameter which is* forwarded in {IERC721Receiver-onERC721Received} to contract recipients.*/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");}
ERC165 是如何防止资产转入黑洞的?interface 是接口的意思,在其中定义的函数可以不实现仅仅放上函数名字相关参数,在程序复杂的时候,相当于目录一般告诉别人我都有什么功能。但是接口的写法各有千秋,名字定义参数类型,甚至是否存在都有不同,所以此提案最终形成了ERC165标准,规范了接口的识别规则:interface ERC165 {/// @notice 查询合约所实现的接口/// @param interfaceID 参数:接口ID/// @return true 如果函数实现了 interfaceID (interfaceID 不为 0xffffffff )返回true, 否则为 falsefunction supportsInterface(bytes4 interfaceID) external view returns (bool);}
supportsInterface 函数,并且其符合165标准supportsInterface 函数,判断是否具有转出NFT的函数IERC721Receiver.sol 拓展包来实现)transfer 和 transferFrom,作用于两种场景:transfer 转移:由用户调用,将本消息发送的钱包所持有的NFTID转移到指定地址transferFrom 从转移:用某机构调用,需要用户先授权某地址,让其有权可转移transfer 就是现金交易,从自己口袋里拿钱支付transferFrom 就是扫码扣款,由店家申请扣款,受制于用户是否开通小额代扣权限transfer 是如何进行的from 方是否是此NFTID的持有者,并且限制该NFT转入0x00地址。其次进行 from 转出地址和 to 转入地址的余额刷新,修改 _balances 全局变量并且重新设置 _owners 此NFTID的所有者地址修改为 to。_approve(address(0), tokenId); 清空历史授权,如果没有这一步,则资产完成了转移,但是其NFTID的转移授权依旧在,细思极恐: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);// Clear approvals from the previous owner_balances[from] -= 1;_balances[to] += 1;_owners[tokenId] = to;emit Transfer(from, to, tokenId);_afterTokenTransfer(from, to, tokenId);}
transferFrom是如何进行的_safeTransfer 所以他的核心逻辑是 require 部分,这的一大细节是:_msgSender() 这是 openzepplin 的标准库 Context.sol 中的方法。msg.sender 是考虑到,可能存在一种交易类型“元交易” ,即交易的付费gas方和交易发起方不相同的情况。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);}IERC721Metadata.sol。要放什么都可以,但是项目方往往在链上只存储最基础的ID+IPFS的地址。Etherscan教程方法来看看一些项目数据有什么,先取Azuki上合约地址:Read Contract 可以查阅到,其元数据只存放了ipfs上的指向地址。而近期兴起的Metaverse项目元宇宙土地Sandbox和Decentraland ,以及去年火热的Axie Infinity ,基本链上存储元数据也只是ID+网址:每笔交易都有21000 GAS需要支付
为交易的每个非零字节数据或代码支付68 GAS
为交易的每个零字节数据或代码支付4 GAS
Mint 的时候,登记上若干NFT属性信息,交易的data部分会将abc等字符转成2个十六进制表示,而每个字符为一个八位二进制,等于一个byte。所以可以约等于将data的长度除以2作为byte数。20gwei的gas价格和2000的eth兑换美元价格,可以估算出,每上链1kb数据在交易发起端就要:20*(21000+68000)*1e9/1e18 * 2000 = 3.5美金EIP1283),来分析具体的消费量,代码具体在函数内,太长了不全粘来:func gasSStore(evm *EVM, contract *Contract, stack *Stack, mem *Memory, memorySize uint64) (uint64, error)历史上GAS消耗的估算有经过若干迭代,如果是 Petersburg或者Constantinople未激活的话,则不按下面逻辑进行计算
32byte,1kb存储则是32个存储槽。Mint 的过程是新增存储,所以如果新增1kb的数据存储在链上代价将是64Wgas,换算成金额则是:20*(640000)*1e9/1e18 * 2000 = 25美金
写在最后:
欢迎各位同学从后台提交有趣的合约或交易哈希。
关注十四,用技术的眼光发现价值。
看到这里,帅气的你不点个赞吗?