Flow作为专门为NFT设计的区块链,其合约语言主要侧重于NFT功能,当然,实现基本的ERC20 token 更不在话下。
由于ERC20合约相对比较简单,迁移也比较容易,因此,本节在完成迁移讲解的同时,重点对比下ERC20 token和Flow token合约的差异,并探讨下Flow的token 空投问题。
标准ERC20合约分析
以太坊ERC20 Token核心功能大致是这样的:
contract ERC20Basic {
mapping(address => uint256) balances; // 余额
function transferFrom(address _from, address _to, uint256 _value) public returns (bool) {
balances[_from] = balances[_from].sub(_value);
balances[_to] = balances[_to].add(_value);
return true;
}
}
如果完整对应的改成Flow Cadence合约的形式,可以是这样的:
contract ERC20Basic {
access(contract) let balances: {Address: UFix64}
pub fun transfer(from: Address, to: Address, amount: UFix64) {
balances[from] = balances[from] - amount
balances[to] = balances[to] + amount
}
}
这样的实现是完全可以的,但就无法享受flow Cadence编程的各种自带好处了。举个简单的例子,solidity使用中会经常担心,把token发送到未知/错误地址,直接就”烧纸”了,但这在Cadence中是不会出现的。因为Cadence中钱包必须先执行金库的初始化操作,也就是先开户,才能转账,一个未知/错误的地址肯定是无法开户的,即使转错了,系统就会报错回滚,不用担心转错账的问题。这个就是面向资源编程的优势体现。
合约迁移
按照程序分析的一般套路,还是先分析数据结构,然后再看对应的增删改查功能函数。
ERC20和Flow推荐token标准合约数据结构对应关系如下图所示:
图1 ERC20和Flow Token数据结构对应图
ERC20的核心数据结构就是一个字典,key为用户钱包地址,而value则是对应钱包的余额,所有转账查询等操作都是基于这个字典展开的,这样的方式类似于银行账号,其实是一种中心化的实现。而Cadence则只需定义了余额,token是作为一个资源类型,存放在对应用户的存储空间内的,也就是vault金库路径里面。也就是说Flow Cadence是一种真正的“去中心化”,用户的token都存储在自己的账号下,而以太坊Solidity的token则是存放在合约账号上的。
举个例子,如果合约不开源,Solidity完全可以随意操纵用户账号,类似某些城乡银行的高端操作,而Flow Cadence合约中,即使铸造token的合约都消失了、重写了,用户的token也不会受到任何影响,更不会存在合约账号暗箱操纵用户token的问题。
ERC20和Flow推荐的Token标准合约的功能函数对应关系如下图所示:
图2 ERC20和Flow Token功能结构对应图
Flow token提供了token相关的铸造、转账、查询功能,但并不提供Approve相关的三方托管功能。具体迁移的时候,只需迁移核心的mint和transfer功能即可,approve的功能可以忽略,保留核心功能就行。Flow相关的approve托管合约,可通过ERC20类似的中心化实现试试,也可以利用“能力”相关的功能实现,具体可参考NFT市场一节。
Cadence上已经有类似ERC20的基础标准合约。我们就以openzeppelin对应实现的基础ERC20合约为准(https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/token/ERC20/ERC20.sol),对应的为Flow推荐的元数据标准合约实现(https://github.com/onflow/flow-ft),然后找到对齐关系,看需要修改的Cadence继承实现代码即可。这里暂时不考虑各种合约扩展功能。
从数据结构和功能函数来看, Solidity ERC721标准合约和flow cadence NFT合约都存在完整的对应关系,对于标准Token合约的迁移,我们只需要对flow合约进行“字符串替换”即可,具体如下图所示:
图3 ERC20合约迁移具体流程
如上图所示,核心就是修改合约名称,代码中对应合约名称的引用部分,也进行对应的修改即可。包括合约、交易、脚本都按此修改即可。合约部分有资源集接口,也进行对应的字符串修改即可。另外还有包含Token名称的资源路径和函数名称,替换成新的合约名称字符串即可。
熟悉linux shell脚本的同学,使用一句shell脚本即可完成上面所说的替换:
#!/bin/sh
nft_name="Wow" #token名称
find ./ -name "*.cdc"|xargs -i sed 's/Example/'''${nft_name}'''/g' {}
理解资源
分析Flow token和ERC20 token的转账功能,可以非常清晰的看到flow“资源”的使用方式:
transaction(amount: UFix64, to: Address) {
let sentVault: @FungibleToken.Vault
prepare(signer: AuthAccount) {
let vaultRef = signer.borrow<&ExampleToken.Vault>(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's Vault!")
self.sentVault <- vaultRef.withdraw(amount: amount) //取出token
}
execute {
let recipient = getAccount(to)
let receiverRef = recipient.getCapability(ExampleToken.ReceiverPublicPath)
.borrow<&{FungibleToken.Receiver}>()
?? panic("Could not borrow receiver reference to the recipient's Vault")
receiverRef.deposit(from: <-self.sentVault) //存放token
}
}
ERC20的token,就是合约的字典数据中,先从源账号key中减去一个值,然后在目标账号key中加一个值。
Flow的token,则先从源账号的金库中withdraw取出一定的token,然后再deposit存储到模板账号的金库中。
具体对比如下图所示,flow这样的方式好处是啥,有啥不足,除了前面说的安全性之外,大家可以自己想一想。
图4 以太坊和Flow转账模式对比
Flow空投
Flow面向资源编程带来安全性无可比拟,但相应的,所得必有所失。在Flow的空投上,就会存在一定的问题。
因为Flow的账号转账,不管是NFT,还是一般的token,目标账号都必须先授权并初始化一个对应vault仓库,才能进行转账操作。类似于你用身份证当钱包地址,但如果在不同的银行存钱取钱,还是要用身份证去对应银行“开户”。
简而言之,Flow没法像以太坊那样,无需用户许可、骚扰式的发送token,这对很多应用而言,少了一个重要的推广方式,类似于不能发骚扰短信、不能发垃圾邮件、不能打骚扰电话了。。。。
目前flow社区暂时没有确定的方案,但有一些合约按照如下一些方式,间接的实现了flow空投功能:
最简单的方式,就是先发送,后领取。相当于一种中心化的实现,具体的数据结构设计方面,可以采用本文开始说的那种中心化的Cadence方式,先把发送的token存放到合约的字典数据里面,用户查看时,搜索字典,命中后会提示有token可以领取,然后再把token 转移到用户账号即可。
第二种,交易市场类实现。也就是做一个类似candy糖果类的NFT市场,有用户在这里领token,有开发者在这里发token,当然,如果是空投,设置价格为0即可。