相信大家一定都对代币和NFT非常熟悉了,也知道它们背后的经典代币标准,如ERC-20和ERC-721等。ERC-20是同质化代币,而ERC-721是非同质化的,非常适合用于艺术品或者具备稀缺性的资产。但是这些代币是如何实现的呢?为什么就能适合于相关的应用呢?
这篇文章就带大家来盘点一下这些代币合约的原理、适用范围以及未来的发展趋势,涵盖标准包括ERC-20、ERC-721、ERC-1155以及最近通过的ERC-3525。
(参考的ERC-20、ERC-721、ERC-1155代码来自OpenZeppelin,ERC-3525代币来自Solv Protocol)
在了解代币合约的原理之前,首先要了解其中一个重要的概念——哈希表(Mapping),简单来说它是一种可以利用关键词(Key)快速查找值(Value)的映射数据结构。代币合约利用哈希表来储存资产信息、被授权人信息等。具体哈希表介绍可以看这里: https://en.wikipedia.org/wiki/Hash_table
代币都有自己的Data Hashing的方式,这个方式会决定代币功能,可以理解为代币的资产构造以及记录的方式。Data Hashing按目的可以分为总体账号以及个体账号。总体账号记录代币的整体资产情况(包括代币种类、个数和整体授权等等),同时也有被授权人/管理人的设置(被授权人自由传输/再授权资产拥有者的资产)。
功能指的是基于代币的Data Hashing设计,代币的一些可实现功能(即public函数),其中包括资产的查询传输、铸造销毁等。
下文将会用这两个方面对各类型的代币标准进行盘点。
总体账户:ERC-20通过一个哈希表来管理和记录代币总体的资产,其中Key是用户地址,映射的Value是正整数用以记录每个地址拥有代币的个数。
个体账户:ERC-20由于本身的简单性,一个记录总体的哈希表即可记录资产的数量,所以仅需要一个登记个人账户被授权人的表即可。这个表是一个以单个地址为Key映射另一个以地址为Key正整数为目标的表:
Mapping(address => (Mapping(address => unit 256))
举例来说,对于代币A下的一个账户,举例0x1,该地址是可以授权别的账户(如0x2,0x3)来代为使用自己的代币的。额度的授权只能由账户本身(即0x1)发起才能生效。
ERC-20标准基本满足了流通货币、股权和大宗商品等概念的代币所需要的条件,而且简单好用。
总体账户:
这个表是把正整数映射给地址,即理解为id为Key,地址为Value,一个id能且只能对应一个地址。这实现了NFT的独一性以及稀缺性。
个体账户:
对比ERC-20的一个表记录所有地址的资产数额,721增加了个体账户资产记录的表,这个表以个人地址为Key,个人地址拥有的NFT数量为Value,记录了地址拥有总的NFT的个数;但是并没有记录个体账户拥有NFT的具体ID,这只能通过event查询。
被授权人授权方面,个人账户有一个整体被授权人,这个被授权地址可以操作该地址在账户中所有的该合约代币。授权人只能是个人地址本身。
另一方面,单独id的NFT也可以被独立授权,被授权人可以操作该id的NFT。授权人可以是整体被授权人或者NFT拥有者本身。
总的来说,个人地址的被授权人可以有无数个,而且他们可以更改个人地址所有的id被授权人地址,而个体id的被授权人地址只能有一个。
举个例子来说,0x1拥有某系列3个NFT,id分别是1,2,3。0x1可以把自己这个系列授权给无数个别的地址,比如它设置给了0x2,那么0x2就有资格传输1,2,3这个3个NFT,同时0x2也有资格把单个id,如1,的传输权授予给其他地址,如0x3。这时候0x3可以自由传输1这个id的NFT。
**721实现了数字艺术品的单一性和稀缺性,即一个id为一个藏品而且只能被一个地址拥有。**而收藏者也可以收藏一个系列的多个id,同时支持查看个数。然而可能考虑到gas的消耗,721合约并不支持单个地址的所有NFT id咨询,比如0x1持有了2个该系列的NFT,通过合约功能仅仅可以查到0x1有2个NFT,但是并不知道是哪两个。想知道0x1持有的具体NFT id,就只能通过合约的log/event去查找。
在ERC-1155里面,总体账户和个体账户同时实现。利用地址指向一个Mapping的Mapping完成了记录。
这里我们可以发现一个有趣的事实,就是1155的设计其实就是20和721的某种融合,可以理解为多个20标准和721标准(把数量设置为1地址只对应一个)的代币融合为一个整体的代币管理方案。
授权管理方面,没有了个体id的授权,只有个体账户的全部授权,可以简化授权的复杂度。
总结来说,我们发现**1155严格来说并不是某一个代币的标准,而是一个代币的管理集合。整体合约甚至没有Name或者Symbol,而且一个id允许被多个地址同时拥有,这让一个id可以选择拥有同质化或非同质化的性质。**使用场景是适用于有多代币需求的情况,比如游戏中道具实现,从非同质化金币或者材料,到独一性的神器都可以用一个1155合约解决。或者可用于一个比较小型的整体生态,比如DEX的LP Token集合等。
同时**由于有了数值的概念,如果设置一个id只有一个人拥有(可通过后续的合约实现),1155可以成为一个有数值概念的721(即Semi-Fungible Token的概念)。**这意味着它有了NFT的可更新性。比如信用方面可以更新用户信用分数,而且可以构建多信用行为体系,每个信用的方面都有一个数值等级。
不过笔者个人觉得由于它对于授权的管理过于简单(只有全部授权,没有单个id的授权,也没有额度的授权),并不适用于大生态的代币系统管理;而且也没有完整的个人账户记录,即通过个人地址作为Key去Map出全部的相关id的资产。这个也只能通过log/event读取。
相比之下,这是一个复杂的合约,但是功能记录等都十分完善,可定制性也很高。
总体账户:3525在这方面创新地先构建了一个取名TokenData的struct作为单个id的描述。不了解代码的同学可以把这个理解为一个id的描述小卡片。内容包括了:id、Slot(很重要,3525的一个核心创新)、在这个id之下的代币数量、所有人、id的被授权人(只有一个)、可以花费这个id余额的被授权地址。
有了这个小卡片,我们就可以把每个id的信息都记录下来。然后通过一个叫_allTokens的list把一张张的小卡片放进去保存起来。
_allTokens: [ TokenData, TokenData, TokenData............ ]
那当我们要取出查找我们想要看的小卡片的时候我们怎么定位呢?3525通过一个id到struct在list中的位置的映射完成。比如我们查到id 214的位置是在1,那么_allTokens[1]就可以拿出小卡片查看。_allTokens这个list记录了该3525的总体资产情况。
id授权方面添加了值的目标,即用id作为Key,标记了各个地址对于这个id的使用额度。
个人账户:3525对于个人账户构建了一个AddressData小卡片和一个哈希表来管理。小卡片内容包括拥有的Token的id的列表,拥有的id在总体账户list的位置的哈希表,账户的被授权人的哈希表。即小卡片实现了记录个人持有的id(每一个都记录)、以及账户被授权人。
后面再通过一个映射把个人地址作为Key去记录了对应的小卡片(AddressData)信息。
整体来看,3525的数据记录非常全面,而且也很有条理。但是到目前为止,和1155相比,3525并无太大不同,只是记录更加完善而已。下面要讲讲Slot,这个放在id小卡片里面的一个变量,正是这个变量让3525高度可定制化,能做1155不能做的事情。
简单来说,Slot是一个Struct,和前面介绍的AddressData和TokenData一样。但是在3525中,Slot这个Struct是需要开发者自定义的,即开发者可以根据自己产品的需求,给Slot添加变量的数量和类型。以Solv Protocol的一个可转债类型债券作为例子,Slot可以包含的信息有到期时间(Maturity Date)、行权价格(Conversion Price)、以及行权价值(Convertible)。加上之前的ID(#6800)、Balance(2400 USDC)等变量,就构建出了一个可转债的NFT。
由于3525的记帐本身非常全面,所以其功能也非常多,想象空间大。其中关键的是实现了id数值的传输以及个人拥有的id之间的数值传输功能,这个实现可以让NFT有了更能多可能。
比如由于有id数值传输的功能,NFT可以更新自己的数值状态。对于信用、灵魂绑定类NFT来说,如果只是简单不可传输的721是不可以实现所有功能需求的。因为个人信用应该是一个动态的过程。这时候3525可以通过改变数值来反应个人信用的变化,又或者可以用于类似Uniswap V3那种NFT仓位中,可以让用户更方便地更新仓位大小。不过其实这个特性1155也可以实现,只要在1155中把每个id的拥有者设为只有一个地址上就行了(虽然在Data Hashing结构上不是,但是可以后期实现),这时候也可以传输/更新数值;而且1155设计更加简洁,甚至在多代币需求的场景下1155有更好的适用性。
**个人拥有的id之间的数值传输,这是3525在功能方面最大区别于1155和721的方面。**这个功能的用例可以在金融中。比如两个不同期限的交割合约的展期,又或者不同金融账号之间的切换等(如现货账户到期货账户的转换,类比CEX的设计)。这个时候每个id就是一个金融账号,用Slot记录账号信息,数值就是账号余额。又比如V3的Range管理(不仅仅是数值),通过Slot记录Range,个人想改变仓位Range的时候通过id到id的数值传输就可以做到。
3525也留下了一些小遗憾。比如,可能是出于重叠id的问题又或者是便于管理的考虑,3525不能自定义id的名字,只能从0开始逐步叠加,即每一个新的NFT的id只能是以【系列总数量+1】开始。再者,在一些extension中,3525虽然做了Slot的记录以及排序,但是缺少了基于Slot的id分类/查询。笔者觉得这在很多的产品设计中可能是一个重要的方向以及用例,比如债券类产品,它们通过Slot记录不同的债的信息然后发行,如果可以用Slot把相同的期限id债券分出,则可以构建更方便功能化的交易/管理等产品。目前这个目的只可以通过查询id的Slot信息,然后通过这个查询构建Slot和id的DB,又或者通过event和log构建去实现。
总结来看,20、721、1155、3525都有自己独特的使用范例,项目方根据自己代币经济需求去选择最适合的。但是相比之下,新型的3525则是一个比前三者都复杂很多的合约,它不仅继承了前面3个标准的特性,还加入了独特的Slot数据结构,而这让它可以完成一些新的任务。此外,虽然3525功能变多了,但它依然保持记录的条理性,目前感觉是个不错的创新。但是由于更多元的信息记录,可以预见的是3525相对高额的gas成本,以及一些未知的漏洞攻击(毕竟未受到时间检验)。