Sensible Fungible Token 方案 - 陈诚 (cc) #
2021 Bitcoin SV 1st BootCamp - Sensible Contract (3/5)
主要介绍两部分:
- token的实现方案
- 基于token方案我们能做的扩展之一swap
一、Token实现方案 #
我们的token方案是基于utxo模型,逻辑全部由比特币脚本实现。因此继承了utxo的特点高并发以及零确认。同时合约的逻辑会被矿工执行验证,因此有着非常高的安全性。
我们在设计token合约时遵循两个原则:代码精简和可扩展性。
保持代码精简能够减少合约出现bug的几率。
首先token要支持的功能越多,代码就会越多,从而出现bug的几率就越大。而基于utxo的合约,每一个token的utxo都是一份完整的合约。随着token的使用,其utxo的数量会增长的越来越多。因此要修改合约的bug需要更新每一份token utxo,这个代价是非常大的。此外,代码少使得编译出来的token合约size也比较小,能够节约手续费。
在保证合约代码精简的同时,我们还希望token合约能够是可扩展的。对于第三方的开发者来说,他能够无许可的在token基础上开发新的应用,这样才能支持生态的发展。
为了支持token合约的可扩展性,我们引进了 Pay To Contract Hash (p2ch)。一般来说,token是被私钥控制的,用户通过私钥签名对token utxo进行解锁。但是我们通p2ch,把token的控制权从用户手中移交给合约,而合约根据自己的代码逻辑来决定token的行为。这里的合约本质就是一段代码和数据的集合,理论上我们可以实现任意的逻辑在里面。
p2ch有点类似于btc上的pay to script hash (p2sh)。这里简单介绍下p2sh的原理。首先生成锁定脚本,然后对锁定脚本进行hash,得到一个20字节的hash数据。解锁的时候传入锁定脚本和此锁定脚本的解锁参数。矿工会先验证锁定脚本和脚本hash是否匹配,然后再执行锁定脚本里面的逻辑进行解锁。
p2ch也是先生成一个锁定脚本,得到20字节的锁定脚本hash,然后把token从比特币的地址上转移到这个锁定脚本的hash上,我们称为contract hash。
当此token utxo解锁时,首先构造一个交易,把之前生成的锁定脚本,放入到一个输出的脚本中。
然后再构造一个交易,把contract和token分别作为输入。
在token的逻辑中,他会去检查此交易的所有输入中是否存在一个输入,它的锁定脚本是跟自己的contract hash匹配,如果匹配,则token就可以解锁
而contract的解锁条件则是由开发者编码来决定,因此可以支持任意的逻辑。
这是token合约(锁定脚本)的结构,有代码段和数据段组成。中间由OP_RETURN操作符隔开。
数据部分组成:
- flag:sensible
- type:4字节unsigned int
- Type specific data:不同的类型有不同的定义。
token的数据类型定义如下:
- Token name,名字,20字节。
- Token Symbol, 简称,10字节。
- Genesis flag是一个1字节的unsigned int,若等于1表示其为genesis合约,用于创建token。等于0则为普通token合约。后面介绍genesis的作用。
- Decimal num,是1字节的unsigned int,用于表示token amount的小数位。
- Token address使用和bsv legacal address一样的数据格式,保持和bsv转账的一致性。同时也支持script hash。
- Token amount是一个8字节的小端存储的unsigned int, 代表token的数量。
- TokenID 长度为36字节,tokenGenesis的outputpoint。
token合约里面实现了两个功能。转账和从合约解锁。
转账的逻辑需要检查输入的数量和输出的数量是否相等。
为了能够检查数量,我们需要对输入和输出进行遍历。代码实现上需要循环遍历所有的输入输出。但是在bitcoin的脚本里面,遍历是比较特殊的,由于不存在jump的指令,loop在脚本里面会展开,这就意味着:
- 你需要在合约里面就指定好你的loop的次数。
- 你支持的loop次数越多,展开后的代码越大,你的合约就越大,手续费就越多。
这是token合约里面用到的某个合约的例子。因为逻辑主要都在loop里面,因此size跟输入输出数量是一个线性的关系。
因此如果要支持更大的输入输出,合约就会变大,转账就会付出更多的手续费 但是大规模的输入输出只有很少的情况下才会用到,这会是的平时在使用较少的输入输出时也需要付出很大一笔手续费,对用户来说有点不太合理。
为了解决上面的问题,我们引入了合约拆分的技术。将数量检查这个功能独立出来做成合约。并且Token能够支持多种输入输出的组合。使用对contract hash检查的技术,能够在较少的代价下支持多种组合。
转账交易时,先根据具体的输入和输出token的数量,选择合适的数据检查合约,然后加入到转账交易的输入中。输入的token合约会检查此交易的输入中是否存在数量检查的合约,如果不存在,则无法解锁。
合约解锁功能也需要对输入输出的数量进行检查,检查的方法和转账类似。Token合约会在输入中检查是否存在数量检查的合约,同时还会检查是否有匹配自己的Contract hash的合约存在,如果不存在则无法解锁。
token合约在发行时能够选择支持增发和禁止增发。我们引入了TokenGenesis合约用来做token的增发。
如果发行者需要保留增发的能力,在第一次发行token的时候,在交易的输出中加入新的tokenGenesis合约。此合约解锁时需要发行者的私钥进行签名,因此只有发行者能够进行增发。
如果需要禁止增发,则在发行token的时候选择不输出tokenGenesis。由于tokenGenesis合约会对自己做溯源检查,因此一旦发行者选择不输出tokenGenesish合约,后面再也无法对token进行增发,能够在程序逻辑上保证发行者无法增发。
二、基于token的扩展:swap #
第二部分主要介绍下我们基于之前提到的token方案,在上面做的扩展开发,实现了一个类似uniswap功能的swap合约。此合约支持四种功能。
上面是测试网上部署的合约四种功能的交易。
同样的,我们根据这四种功能对合约进行了拆分,除了一个主合约swapMain,还有分别实现四种功能的合约。每当需要调用某个功能时,就生成对应功能的合约。
增加流动性的交易会需要先生成addLiquidity和amountCheck的合约,然后根据增加的流动性增发出一定数量的lp token。而转入到交换池里面的合约是用fetchToken合约控制的。
FetchToken合约的解锁条件是只要输入中有三种合约中的任意一中即可。
提取流动性的交易构成如上所示。
这是使用bsv换取token合约的功能。
这是token换取bsv的交易。
以上的swap合约只是token扩展的一个实例,我们还可以使用这种技术实现更多的基于token的扩展。