以太坊Solidity迁移Flow Cadence指南7-ERC20/TOKEN迁移

博客讨论了将ERC20合约迁移到Flow区块链的过程,强调了Flow的 Cadence语言在安全性方面的优势,如防止转错账。Flow的token存储在用户账户内,实现真正的去中心化。迁移主要涉及mint和transfer功能,approve功能可忽略。此外,Flow的空投机制因账号需预先授权而面临挑战,提出了两种可能的解决方案。

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即可。

你遇到的错误: ``` Number of inputs required by this Simulink or Stateflow function does not match the call site. State 'Check' "Check_isSorted(Sig_U8_Table_X)" Component: Stateflow | Category: Coder error ``` --- ## 🔍 错误原因 这个错误的意思是: > **你在状态 `Check` 中调用了函数 `Check_isSorted(Sig_U8_Table_X)`,但该函数定义时所需的输入参数个数与你实际传入的不一致。** 换句话说: - 你调用函数时只传了一个参数:`Sig_U8_Table_X` - 但函数 `Check_isSorted` 可能定义为需要 **更多参数**(比如两个或以上),或者根本没有这样命名的函数。 - 或者相反:函数不需要参数,但你却传了一个。 --- ## ✅ 解决方案步骤 ### 步骤 1:确认 `Check_isSorted` 是什么类型的函数? 在 Stateflow 中有几种“函数”形式: | 类型 | 特点 | |------|------| | **Simulink Function Block** | 在模型中定义,在 Stateflow 调用 | | **MATLAB Function Block** | 内嵌代码块,可被外部调用 | | **Graphical Function / Scripted Function in Stateflow** | 定义在 chart 内部的 reusable 函数 | 你需要检查:`Check_isSorted` 是在哪定义的?它有几个输入参数? --- ### ✅ 步骤 2:打开并检查函数定义 #### 情况一:`Check_isSorted` 是一个 **Stateflow 图形函数(Graphical Function)** 1. 打开你的 Stateflow Chart 2. 查看左侧或右侧是否有名为 `Check_isSorted` 的函数图标 3. 双击进入编辑界面 4. 看它的 **参数列表**,例如: ```matlab function y = Check_isSorted(table_X, n) % 假设这里有两个输入:table_X 和 n y = all(diff(table_X(1:n)) > 0); end ``` 👉 那么你就必须传两个参数! ❌ 错误调用: ```matlab Check_isSorted(Sig_U8_Table_X) % 少了一个参数 n ``` ✅ 正确调用: ```matlab Check_isSorted(Sig_U8_Table_X, 10) % 补上第二个参数 ``` --- #### 情况二:`Check_isSorted` 是 **Simulink Function Block** 1. 在模型中搜索名为 `Check_isSorted` 的模块 2. 双击打开其内部子系统 3. 查看里面的 **Inport 数量和名称** 4. 回到 Stateflow 调用处,确保传递的参数数量匹配 例如,如果 Simulink Function 定义了两个输入端口: - `u1`: `Sig_U8_Table_X` - `u2`: `length` 那你必须这样调用: ```matlab Check_isSorted(Sig_U8_Table_X, 10) ``` --- #### 情况三:函数根本不存在 or 名字拼错了 ⚠️ 常见低级错误! 检查是否: - 函数名拼错?比如应该是 `isSorted_Check`? - 函数未定义?你写了调用,但没创建函数本身? 👉 如果函数不存在,编译器会报“找不到函数”或“输入输出不匹配”。 --- ### ✅ 步骤 3:正确示例 —— 定义并调用一个判断递增的函数 #### 示例:在 Stateflow 内部定义图形函数 ```matlab function isInc = Check_isSorted(table_X, len) %#codegen if len <= 1 isInc = true; elseif length(table_X) < len isInc = false; else subset = table_X(1:len); isInc = all(diff(subset) > 0); end ``` 然后在状态 `Check` 的 `entry` 动作中调用: ```text entry: sorted_flag = Check_isSorted(Sig_U8_Table_X, 10); ``` 而不是只写: ```text Check_isSorted(Sig_U8_Table_X) ❌ 少参数! ``` --- ### ✅ 步骤 4:如何避免此类错误? | 最佳实践 | 说明 | |--------|------| | ✅ 使用自动补全 | 输入函数名时按 Tab,看看提示有几个参数 | | ✅ 右键函数 → "Go to Definition" | 快速跳转查看参数列表 | | ✅ 统一管理函数 | 把常用函数集中放在 chart 底部 | | ✅ 添加注释 | 写清楚每个输入参数含义 | --- ### ✅ 附加建议:简化逻辑(推荐) 与其手动调用复杂函数,不如直接在状态内实现简单判断: ```text State: Check entry: if length(Sig_U8_Table_X) >= 10 flag_increasing = all(diff(Sig_U8_Table_X(1:10)) > 0); else flag_increasing = false; end Transition to NextState: Condition: flag_increasing ``` 这样就不需要额外函数,避免调用错误。 --- ## ✅ 总结 | 问题 | 解决方法 | |------|----------| | 输入参数数量不匹配 | 检查函数定义,补全所有输入参数 | | 函数名拼写错误 | 核对大小写、下划线等 | | 函数未定义 | 在 chart 中添加 Graphical Function 或 MATLAB Function | | 索引从 0 开始 | 改为 `()` 和 `1` 起始索引 | --- ###
评论
成就一亿技术人!
拼手气红包6.0元
还能输入1000个字符
 
红包 添加红包
表情包 插入表情
 条评论被折叠 查看
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包

打赏作者

wangliang_f

你的鼓励将是我创作的最大动力

¥1 ¥2 ¥4 ¥6 ¥10 ¥20
扫码支付:¥1
获取中
扫码支付

您的余额不足,请更换扫码支付或充值

打赏作者

实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值