1. NFT基础概念与合约架构
大家好,我是你们的老朋友。今天咱们来聊聊NFT合约的基础知识。说实话,我见过太多人一上来就冲进NFT市场,结果连ERC-721和ERC-1155都分不清——嗯,这其实挺危险的。我自己在审计合约时,就遇到过因为标准选错导致项目重写的案例。
什么是NFT?
NFT,全称是Non-Fungible Token,中文叫「非同质化代币」。说白了,它就是区块链上独一无二的数字资产。你想想看,比特币、以太坊这些是同质化的——你手里的一个BTC和我手里的一个BTC完全一样,可以互换。但NFT不一样,每个都是唯一的,就像你家的猫和我家的猫,虽然都是猫,但长得不一样。
我个人习惯把NFT理解成「数字世界的房产证」。它证明你对某个数字内容拥有所有权。这个内容可以是图片、音乐、视频,甚至是一段文字。我在项目中遇到过有人把推特内容铸造成NFT,结果还真卖出去了——嗯,这世界就是这么神奇。
ERC-721与ERC-1155标准对比
这两个标准是NFT合约的基石。我刚开始接触时也搞混过,后来踩了几个坑才彻底明白。
| 特性 | ERC-721 | ERC-1155 |
|---|---|---|
| 代币类型 | 每个代币唯一 | 支持同质化+非同质化 |
| 批量操作 | 不支持 | 支持批量铸造/转账 |
| Gas成本 | 较高(逐个操作) | 较低(批量分摊) |
| 适用场景 | 数字艺术品、收藏品 | 游戏道具、多品类资产 |
| 合约复杂度 | 简单 | 中等 |
为什么会有两个标准?其实ERC-721是最早的NFT标准,2018年就出来了。后来大家发现游戏里需要同时管理多种资产,比如武器、皮肤、金币,如果每个都单独部署一个ERC-721合约,那Gas费会高得吓人。于是ERC-1155应运而生,它用一个合约管理所有资产类型。
我记得有一次审计一个游戏项目,他们用了ERC-721来管理游戏里的100种道具。结果每次玩家交易道具,都要部署新的合约实例——这简直是灾难。后来我建议他们换成ERC-1155,Gas费直接降了70%。
核心区别一句话总结:ERC-721是「一个合约一种资产」,ERC-1155是「一个合约多种资产」。选哪个,取决于你的业务场景。
NFT合约核心函数解析
不管用哪个标准,有三个函数你必须要搞懂:mint、transfer、burn。这三个函数构成了NFT的生命周期。
1. mint(铸造)
铸造就是把NFT从无到有创造出来。这是整个合约的入口,也是最容易出问题的地方。
// ERC-721 标准铸造函数
function mint(address to, uint256 tokenId) external {
require(msg.sender == owner, "只有合约所有者才能铸造");
_safeMint(to, tokenId);
}
这里有个坑:_safeMint和_mint的区别。我刚开始写合约时用了_mint,结果有用户把NFT转到了合约地址,直接锁死了。后来我才知道,_safeMint会检查接收方是不是合约,如果是合约,必须实现onERC721Received接口,否则交易回滚。嗯,这个教训让我记住了。
避坑指南:我曾经审计过一个项目,他们的mint函数没有做总量限制。结果有人写了个脚本,一次性铸造了10万个NFT,直接把合约的存储撑爆了。所以,mint函数一定要加maxSupply检查。
2. transfer(转账)
转账是NFT流通的核心。ERC-721提供了两种方式:transferFrom和safeTransferFrom。
// ERC-721 标准转账函数
function transferFrom(address from, address to, uint256 tokenId) external {
require(_isApprovedOrOwner(msg.sender, tokenId), "你没有权限");
_transfer(from, to, tokenId);
}
你想想看,为什么要有两个?transferFrom是基础版,它不会检查接收方。而safeTransferFrom会调用接收方的回调函数。我个人习惯在公开接口里只用safeTransferFrom,因为安全第一。
这里有个经典漏洞:重入攻击。攻击者可以在转账回调函数里再次调用转账,导致资产被多次转移。解决方案很简单——使用OpenZeppelin的ReentrancyGuard,或者先更新状态再转账。
3. burn(销毁)
销毁就是把NFT从流通中永久移除。这个函数看似简单,但很多人会忽略一个重要问题:销毁后,元数据怎么办?
// ERC-721 标准销毁函数
function burn(uint256 tokenId) external {
require(_isApprovedOrOwner(msg.sender, tokenId), "你没有权限");
_burn(tokenId);
}
我记得有个项目,用户销毁NFT后,对应的元数据URI还在IPFS上存着。结果有人通过URI找到了原始图片,直接复制粘贴,搞得社区一片混乱。所以,我建议在销毁时,最好把元数据也标记为「已销毁」。
个人经验:我在审计时发现,很多项目把burn函数设成了公开的,任何人都可以调用。这其实很危险。正确的做法是:只有合约所有者或者经过授权的地址才能销毁。或者,你可以设计一个「销毁冷却期」,防止误操作。
知识体系结构图
下面这张图是我自己画的,帮你理清NFT合约的核心脉络:
这张图把NFT合约的核心脉络理清楚了。从上到下,先选标准,再实现三个核心函数,最后别忘了安全防护。每一步都有坑,但只要你按这个框架来,基本不会出大问题。
好了,这一章的内容就到这里。记住,NFT合约看似简单,但细节决定成败。下一章我们会深入分析常见的NFT合约漏洞,到时候我会分享更多实战案例。
本章核心要点:
- NFT是独一无二的数字资产,用ERC-721或ERC-1155标准实现
- ERC-721适合单一资产,ERC-1155适合多品类资产
- mint函数必须加总量限制和权限控制
- transfer函数优先使用safeTransferFrom防丢失
- burn函数要清理元数据,防止信息泄露