原文链接:https://developers.flow.com/cadence/solidity-to-cadence
翻译:Flow 大使 Jing
Cadence 引入了一种不同的方式来开发智能合约,这对于 Solidity 的开发人员来说可能并不熟悉。其思维方式和开发环境都存在差异,还有一些在 Solidity 中没有的新语言特性。本文概述了一些 Flow 和 Cadence 的高级设计理念,以及如何使用 Cadence 实现某些常见的 Solidity 开发任务,这对于理解其差异性至关重要。我们还将详细介绍如何最有效的利用 Cadence 的独特功能以及如何避免在过渡学习过程中可能遇到的陷阱。
Cadence 的基础概念
从 Solidity 过渡到 Cadence 需要习惯的一个根本区别是思维模式。以太坊上的安全性和互操作性是围绕着地址设计的(或更具体地说是与地址关联的帐户),因此所有合约都必须仔细跟踪、评估访问和授权。
交易是基于它们的授权者,这由msg.sender在交易上下文中提供。用户与合约或合约与合约之间的交互必须明确编码,以确保在与合约交互之前获得批准。基于合约的存储意味着以太坊中用户的所有权在 mapping 中表示,例如所有者到余额的映射,或 token ID 到所有者的映射。换句话说,所有权存储在类似于个人银行余额的中央账本中。加密钱包有助于将多种代币类型的余额组合成用户方便的视图。
Cadence 引入了新的原语和功能,即 Resources(资源) 和 Capabilities(能力),它们是围绕 Flow 帐户模型设计的。资源是一种高级语言类型,它们是唯一的、不可复制的并且不能被丢弃的。这些属性使得 Resources 成为代表数字资产的理想选择,例如货币或代币,这些资产的数量总是有限的。资源始终存储在帐户存储中,合约使用 Capabilities 控制对它们的访问。Capabilities 是另一种特殊类型,无需跟踪地址即可保护受保护的资源。对于那些熟悉面向对象编程语言的人来说,Cadence 使这些工作变得简单直观。
Cadence 新手在开发前应确保了解以下几个主要概念。
1. Flow 账户模型
Flow 的账户模型不仅存储账户关联的密钥和代码(“智能合约”),还存储该账户拥有的资产。没错,在 Cadence 中,您的代币存储在您的账户中,而不是智能合约里。当然,智能合约仍然定义了这些资产及其行为方式,但这些资产可以通过 Resources 安全地存储在用户的账户中。
Cadence 中只有一种账户类型,并伴随着账户地址,类似于以太坊中的 Externally-Owned-Account(EOA)地址。然而,与以太坊合约账户不同,Cadence 中的账户还存储合约代码。账户通过成为密钥、资源和合约存储在链上的容器来实现对 Flow 的所有权。
PublicAccount and AuthAccount
PublicAccount是帐户的公开可用部分,可以通过getAccount访问公共函数或对象。
AuthAccount仅对交易的签名者可用。AuthAccount引用提供对该帐户的存储、密钥配置和合约代码的完全访问。Capabilities 确保可以安全地共享 / 访问帐户中持有的资源;AuthAccount永远不应将访问权限授予其他帐户。
2. Resources
资源是唯一的,是一种线性类型,永远不能被复制或隐式丢弃,只能在帐户之间转移。如果在开发过程中,函数无法存储从函数范围内的帐户获得的资源,语义检查将标记错误。运行时也会进行类似操作的严格约束。因此,在退出前未正确处理作用域内资源的合约函数将被中止,并将它们恢复到初始状态。Resources 的这些特性使它们非常适合代表 FT 和 NFT。所有权则通过它们的存储位置进行跟踪,并且资产不会被复制或意外丢失,因为语言本身强制执行该正确性。
Flow 鼓励在链上存储数据和计算,而资源类型使这比以往任何时候都更加容易。由于资源始终存储在帐户中,因此资源实例中存在的任何数据和代码都可以在链上无缝管理,无需任何显式处理。
3. 基于 Capability 的访问
可以通过 Capabilities 来管理对存储的对象的远程访问。这意味着如果一个帐户想要访问另一个帐户的存储对象,则必须为其提供对该对象的有效 Capability。能力可以是公共的也可以是私有的。如果一个帐户想要授予所有其他帐户访问权限,则它可以共享一个公共 Capability。(例如,一个账户通常通过公共 Capability 接收来自其他账户的 FT 代币转账。)或者,一个账户可以向特定账户授予私有能力,以便提供对受限功能的访问。例如,一个 NFT 项目通常通过“管理员能力”来控制铸币,该权限授予特定帐户铸币新代币的权力。
4. 合约标准
为了使生态系统受益,已建立了许多广泛使用的合约标准,例如 Fungible Token (FT) 和 **Non-Fungible Token** (NFT) 标准,它们在概念上等同于以太坊的 ERC-20 和 ERC-721 标准。Cadence 的面向对象设计意味着标准通过合约子类型应用,例如资源、资源接口或合同标准中声明的其他类型。标准可以定义和限制行为和 / 或设置标准的实施不能违反的条件。
有关可用标准和其他核心合同的详细信息,请参阅 Flow 简介。
5. NFT 标准和元数据
Solidity 必须在链下管理 NFT 元数据,并在链上链接到 IPFS JSON。
Cadence NFT 标准为提供了称为 ** 视图的特定内置类型来描述元数据。视图可以在生成时添加到 NFT 中,并且将始终作为 NFT 的一部分数据提供。元数据存储在链上,而图形和视频内容存储在链外。Cadence 为基于 HTTP 和 IPFS 的媒体存储提供实用程序视图,** 这些媒体存储与您的 NFT 保持链接。
想要在 Flow NFT 目录 中列出自己的 NFT,则必须要使用 NFT 元数据视图。我们鼓励项目使用 NFT 目录,因为钱包和其他生态系统合作伙伴可以无缝集成添加到列表里的新 NFT 收藏,而无需项目创建者额外的集成工作。
Flow 上的 NFT 元数据打开了通往更多可能性的大门,帮助建设者进行创新。查看最近的 ** 案例研究 **,其中社区合作伙伴利用基于 SVG 的元数据制作其 PFP 的 2D + 3D 组合版本,所有这些都存储在链上的 NFT 元数据中!
安全和访问控制
去中心化应用程序开发将重点放在安全性和访问上,可以将其描述为一个安全工程。从 Solidity 的角度来看,理解资源、能力和账户模型可以更加容易得解决这个问题。
1. msg.sender 被认为是有害的
每个 Solidity 开发人员在开始使用 Cadence 编程时问的第一个问题是:
“如何获取授权交易的账户?”
在以太坊中,此帐户被称为msg.sender并根据授权人通知函数中的程序流。这样做是访问和安全的关键,也是以太坊上身份和所有权的基础。
Cadence 没有msg.sender也没有交易级别的方式让 Cadence 代码唯一标识出调用帐户。即使有办法访问它,但 Cadence 支持 ** 多重签名 ** 交易,这意味着将返回所有签名者帐户的列表,从而无法识别单个授权者。
不支持或强烈反对使用msg.sender的原因是因为 Cadence 使用 Capabilities 进行访问而不是地址。开发人员需要改变的思维习惯是,授权帐户(在 Cadence 中称为提供者或签署者)必须首先从需要它的合约中获得能力,然后使请求帐户能够访问受保护的函数或资源. 这意味着合约在继续之前永远不需要知道签名者是谁,因为能力就是授权。
基于能力的安全模型在与基于访问的安全模型在相反的方向上构建访问。
2. 使用 Capabilities 进行访问控制
Solidity 缺乏特定类型或其他原语来帮助权限管理。开发人员必须在每个函数入口点内联 require,从而验证交易的msg.sender。
** 能力 ** 是通过将存储路径(合约存储的命名空间)链接到受保护的对象,然后使其他帐户可以使用该链接的能力来定义的。为存储路径和功能定义的公共和私有范围本身与 PublicAccount / **AuthAccount** 帐户范围完全一致。
任何帐户都可以访问帐户的公共能力。公共能力是使用公共路径创建的,即它们具有public域。例如,所有帐户都具有链接到 FlowToken.Vault资源的默认公共功能。此保险库作为公共能力公开,范围限于FungibleToken.Receiver资源接口,以允许任何帐户borrow()引用保险库以进行deposit()。由于仅[FungibleToken.Receiver]() 公开了接口下定义的函数,因此 vault 引用的借用者无法调用withdraw(),因为它在provider 接口范围内。公共能力可以从授权账户(AuthAccount)和公共账户(PublicAccount)获得。
私有能力是专门授予账户的。它们是使用私有路径创建的,即它们具有 private 域。创建后,他们可以从授权帐户对象(AuthAccount)中获取,但不能从公共帐户(PublicAccount)中获取。要与另一个帐户共享私有 Capability,拥有帐户必须 publish 将其发送到另一个帐户,该帐户位于 ** 帐户收件箱中 **。收件人稍后可以使用 thenclaim 函数从帐户收件箱中声明该能力。
Capabilities 可以 unpublished 并且也可以被创建的账户 ** 撤销权限 **。
为了更加自动化,publish,claim和unpublish的 event 会被发送在 Capability 完成操作时。
详细信息可以在Capabilities (https://developers.flow.com/cadence/language#capability-based-access-control) 中找到。
3. 保护价值的安全策略
虽然 Capabilities 授予帐户访问受保护资源的权限,但仍然有必要对通过它们访问的值施加控制。例如,如果您的用例需要委托 FlowToken.Vault 对 withdraw() 资金的访问权限,那么限制金额很重要。实施 FT/NFT 标准的代币是 Flow 上账户交换的主要价值类型。该标准提供了实施能力限制最佳实践所需的原语。
代币隔离
所有 FT 都会封装在VaultResource 中,每个不同的 FT 将作为一个单独Vault保存在帐户中。类似地,所有 NFT 都实现了一个Collection资源,其中存储了该集合帐户所持有的 NFT。
每当必须将withdraw()功能的访问权限委托给另一个帐户时,限制可以提取代币数量的最简单方法是为该代币类型创建一个新资源Vault,并在主代币中移动更少量的代币进Vault。 然后将能力链接到该Vault实例,然后再提供给另一个帐户。
类似的模式可以用于 NFT,其中Collection可以创建一个新的资源,只有那些应该暴露的 NFT 会被移动到其中。然后将能力链接到该Collection实例,然后再提供给另一个帐户。
定制控制策略
对于更复杂的用例,可能会创建一个新资源,该资源实现相关接口以匹配它包装的受保护资源的接口。然后,新资源的代码可以根据需要强制执行限制,并控制如何以及何时对基础资源进行委派。
4. 管理员角色
与 Solidity 相比,在 Cadence 中创建管理员角色需要更多的代码,所有这些都封装在 Resource 中。管理对象设计可以高度自定义,并使用能力进行细粒度控制,例如限制对单个功能的访问,如果需要,可以在每个帐户的基础上进行。管理员角色所需的复杂性可能会有所不同,例如,较大的组织可能需要更复杂的基于角色的访问方案。在此上下文中使用资源是关键 - 因为无法复制实例。第一个被 mint 的管理员资源帐户充当 root 管理员。管理员可以实现创建额外的管理员资源实例,只有 root 管理员可以通过 Capability 将其授予选定的用户帐户。方便的是,管理员角色只能通过 Capability 访问,并能被很轻松的 ** 撤销能力 **。
admin 角色源自 **init 单例模式 **,并使用 **Capability Bootstrapping** 模式使 Capability 可用于其他帐户。
**Cadence cookbook** 中提供了一个示例管理员角色实现。
基于角色的访问
可以通过将角色定义为由 root-admin 帐户管理的资源来实现基于角色的访问。角色可以提供对保护其他受保护资源的能力的有限访问,访问级别的因角色而异。root-admin 可以通过私有功能授予帐户对各个角色的访问权限。允许角色调用的函数可以限定范围access(contract)以强制它们只能由根管理合约中的代码路径调用。
其他最佳实践和范例
某些公认的 Solidity 最佳实践可能不适用或处理方式不同。
1. Check effects interactions
Solidity 合约必须使用 ** 检查效果交互 ** ,因为默认情况下函数是公共的,并且基于地址的访问意味着当程序流将控制权让给外部合约时必须存在守卫。这在 Cadence 中不再是问题。因为默认情况下,函数是私有的,并且语言本身提供了一系列 ** 访问范围 **。更重要的是,“与将控制权交给外部合约相关的风险”是以太坊现象;风险不再适用。这主要是因为 Cadence 合约不是静态单例,因此在交易范围内,控制权永远不会丢给另一个合约。
2. Guard Check
Solidity 使用revert, require&assert来验证输入。require 是 Solidity 基于地址的特性的产物,被 Capabilities 取代。revert与 Cadence 的相似之处panic在于事务被中止。Cadence 提供了一个在 Solidity 中镜像的操作符assert。
3. 修饰符
在函数中强制执行预检查时,修饰符在 Solidity 中被广泛使用。这是一个强大的语言特性。但是,修饰符也可以改变状态,这会给程序控制流带来风险。
Cadence 使用pre和post块来验证输入值或函数执行输出。值得注意的是,pre和 post block 禁止改变状态并且只能强制执行条件。
另一个区别是 Solidity 中的修改器可以在合约中多次重复使用。Cadence pre& post blocks 仅与单个功能相关联,减少了出错的可能性,但会导致少量代码重复。
4. 错误处理
Solidity 提供 try/catch 块来处理错误,但是,目前在 Cadence 中没有等效项。
整合差异
1. 脚本和交易
Cadence 和 Solidity 之间的另一个主要区别是部署的合约不是在 VM 中执行的唯一代码。Cadence 提供脚本,其中一个子集是事务,并且都允许执行任意代码。脚本或交易不部署在链上,而是始终存在于链下,但是,它们是运行时执行的顶级代码负载。客户端通过 Flow Access API gRPC 或 REST 端点发送脚本和事务,并在适用时将结果返回给客户端。脚本和交易能够以更有效和更强大的方式将 dapp 与底层区块链集成,其中合约可以更纯粹地被视为服务或组件,而脚本或交易成为链交互的特定于 dapp 的 API 接口。
脚本本质上是只读的,只需要一个函数声明,并针对链状态main执行 查询,例如:
// This script reads the balance field of an account's ExampleToken Balance
import FungibleToken from "../../contracts/FungibleToken.cdc"
import ExampleToken from "../../contracts/ExampleToken.cdc"
pub fun main(account: Address): UFix64 {
let acct = getAccount(account)
let vaultRef = acct.getCapability(ExampleToken.VaultPublicPath)
.borrow<&ExampleToken.Vault{FungibleToken.Balance}>()
?? panic("Could not borrow Balance reference to the Vault")
return vaultRef.balance
}
交易是脚本的 ACID(原子的、一致的、隔离的和持久的)版本,只有prepare和execute函数要么在完整的链状态中成功并如上所述改变链状态,要么失败并且什么都不改变。它们还支持设置pre 和post条件。在下面的示例交易中,ExampleTokens 被存入多个输入的receiver 映射列表中。
import FungibleToken from "../contracts/FungibleToken.cdc"
import ExampleToken from "../contracts/ExampleToken.cdc"
/// Transfers tokens to a list of addresses specified in the `addressAmountMap` parameter
transaction(addressAmountMap: {Address: UFix64}) {
// The Vault resource that holds the tokens that are being transferred
let vaultRef: &ExampleToken.Vault
prepare(signer: AuthAccount) {
// Get a reference to the signer's stored vault
self.vaultRef = signer.borrow<&ExampleToken.Vault>(from: ExampleToken.VaultStoragePath)
?? panic("Could not borrow reference to the owner's Vault!")
}
execute {
for address in addressAmountMap.keys {
// Withdraw tokens from the signer's stored vault
let sentVault <- self.vaultRef.withdraw(amount: addressAmountMap[address]!)
// Get the recipient's public account object
let recipient = getAccount(address)
// Get a reference to the recipient's Receiver
let receiverRef = recipient.getCapability(ExampleToken.ReceiverPublicPath)
.borrow<&{FungibleToken.Receiver}>()
?? panic("Could not borrow receiver reference to the recipient's Vault")
// Deposit the withdrawn tokens in the recipient's receiver
receiverRef.deposit(from: <-sentVault)
}
}
}
交易可以包含任意数量的取款 / 存款,跨多个 FT,发送到多个地址,或其他更复杂的变化,所有这些都将根据其 ACID 属性完全成功或失败。
2. 合约导入和动态合约借用
以太坊中的合约类似于静态单例,因为交互直接发生在用户和合约实例本身上声明的函数之间。Cadence 的面向对象特性意味着合约更准确地被视为导入的依赖项。导入的合约使其对象图在运行时可用于代码。访问功能的帐户交互不是与合约单例实例交互,而是主要的集成入口点,允许用户与返回的对象进行交互。
合约的动态借用是根据合约地址内联加载合约。可以将加载的合约转换为它符合的合约标准,例如:NFT 标准,然后以与导入时相同的方式进行交互。考虑这对合同的可组合性的影响..
有关部署、更新、删除或借用合约的详细信息,请参见合约(https://developers.flow.com/cadence/language#contracts)
3. 多密钥、多重签名支持
Solidity 仅支持一种多重签名方案,其中需要获得 m 中的 n(假设 m > n)个批准才能从多重签名智能合约执行交易。以太坊生态系统中使用最多的多重签名智能合约是 gnosis safe contract。然而,Solidity 缺乏对签名聚合或 BLS 签名方案的支持。
Cadence 提供了广泛的选项来实现各种多重签名方案。
对多签交易的固有支持。
资源转移计划。
内在支持 BLS 签名方案。
Flow 账户密钥已分配权重,其中 1000 单位权重是签名密钥成功执行交易所需的累积权重。可以将权重分配给多个密钥,并将这些部分加权密钥分发给授权签名者。签署交易时,所有签署者必须在短时间内一起签署交易,以便累计权重达到 1000 个单位。
有关 BLS 签名的固有支持的详细概述,请参阅 BLS 签名方案 (https://developers.flow.com/cadence/language/crypto#bls-multi-signature)
资源转移方案
多重签名交易的主要限制是必须在相对较短的时间窗口内对交易进行全部签名。如果错过了这个窗口,交易将中止。资源转移方案与 Solidity 多重签名智能合约非常相似。创建具有代理执行资金转移功能的资源。该资源从一个签名者交给下一个签名者以收集签名。一旦达到所需签名的阈值,交易就会被执行。经过的时间 这种方法的主要缺点是不支持执行任意功能。
其他平台差异
以下与 Cadence 合约无关的差异有助于理解应用程序设计。
1. 事件
Flow 广泛使用事件来向链下系统提供有关交易期间发生的特定操作的实时信号。Flow 的主要区别在于事件保留为历史记录的一部分,不会从存储中清除。事件可以填充任意数据,以帮助事件的消费者。鼓励构建者在用户执行交易时利用事件实现无缝 UX。
2. 合约可升级性
Flow 支持 Cadence 合约的有限可升级性,这在开发过程中最有帮助。以下函数显示了帐户所有者如何更新合同。一旦上传升级,就会分析升级是否存在禁止的更改。可升级性仍是早期阶段的功能,会随着时间的推移不断改进。
fun update__experimental(name: String, code: [UInt8]): DeployedContract
为了在合约经过测试并准备好部署后强制执行不变性,账户所有者可以选择从包含合约的账户中撤销密钥。
有关节奏可升级性的详细信息,请参阅 Contract updatablebility。
3. 帐户密钥制定
在基于 EVM 的链中,地址源自加密生成的公钥,并且可以有一个私钥,支持一种类型的签名曲线,即 ECDSA。它们不可在链下验证,地址中的拼写错误 / 截断可能会导致资金丢失。
Flow 账户地址具有特殊格式,可在链下验证。可以使用基于线性码的错误检测算法来验证地址格式的有效性。虽然这也不能确认地址在链上是活跃的,但额外的可验证性是一种有用的保障。
4. 合同规模限制
Solidity 开发人员会很清楚 EIP-170 可部署合约字节码的大小限制为 24KB。这会给需要优化合约字节码大小的构建者带来负担,有时甚至需要重新设计合约以将其分解为更小的合约部分。
相比之下,Cadence 没有固有或定义的智能合约大小限制。但是,它受到 1.5MB 的交易大小限制的限制。除了极少数例外,此限制不太可能对那些开发 Cadence 合约的人造成问题。如果需要,有一种已知的方法可以部署超过 1.5 MB 的合约,我们将在稍后记录。
基本语言差异
1. 算术
从历史上看,Solidity、智能合约由于对算术欠载 / 溢出的处理不当而损失了数百万美元。当前的 Solidity 版本为算术运算提供了内置的下溢 / 溢出处理。
Cadence 实现了避免上溢 / 下溢的饱和数学。
2. Optional 类型支持
可选绑定提供对 nil 值的内置条件处理。Cadence 中的常规数据类型必须始终有一个值并且不能为 nil。Optionals 启用可能包含特定类型或 nil 值的变量 / 常量 Optionals 有两种情况:要么有值,要么什么都没有;他们 fork 程序流程类似于 if nil; else; end;.
3. 可迭代字典
Solidity 提供了映射类型,但是它是不可迭代的。因此,dApp 开发人员必须维护链下跟踪才能访问密钥。这也促使构建者创建自定义数据类型 EnumerableMap ,这会增加 gas 成本。
Cadence 提供了 Dictionary 类型,它是可迭代的键值关联的无序集合。
4. 对类型函数的丰富支持
Cadence 提供了许多原生类型的实用函数来简化开发。例如,String 类型提供:
utf8
concat()
slice()
decodeHex()
encodeHex()
toLower()
length
5. 参数标签
Cadence 中的参数标签有助于消除输入值的歧义。它们使代码更具可读性和明确性。当使用相同类型时,它们还消除了围绕参数顺序的混淆。它们必须包含在函数调用中。_ 如果标签在其声明之前有 ,则可以跳过此限制。
例如: fun foo(balance: UFix64) 称为 self.foo(balance: 30.0)
fun foo( _balance: UFix64) 可以称为 as self.foo(balance: 30.0) 或 as self.foo(30.0)
关注 Flow
什么是 Flow 福洛链?
Flow 福洛链是一个快速,去中心化,且对开发者友好的区块链,旨在为新一代游戏、娱乐应用程序提供动力的数字资产的基础。Flow 是唯一一个由始至终为消费者提供出色体验的 Layer-1 区块链团队。其团队创造的 dApp 包括:CryptoKitties、Dapper Wallets、NBA Top shot。
CrytoKitties 于 2017 年推出时便快速成为加密市场最受欢迎的 dApp,因其成功而导致以太坊堵塞。在 Flow 上运营的 NBA Top shot 也已成为增长最快的 dApp,在公开发布后的 6 个月创造了 7 亿美金销量。正因为 Flow 公链的可扩展性和消费者友好的体验,让这一切成为可能。目前有 1000 多个项目正在 Flow 链上筹备中,我们期待看到一个伟大的生态系统蓬勃发展。
关于 Dapper Labs
Dapper Labs 是一家位于加拿大的全球顶尖区块链服务商,在 2017 年年底通过 CryptoKitties 收藏游戏成功进入⽤户视野,并且因为加密猫的爆⽕导致以太坊拥堵,从而推出 Flow 公链以及全新的开发语言—— Cadence,旨在吸引更多的开发者在 Flow 上开发应⽤。
Flow 的合作伙伴们:
我们欢迎越来越多的小伙伴加入 Flow 星球,为星球增添色彩!
Flow 官网:https://zh.onflow.org/
Flow 论坛: https://forum.onflow.org/
Flow Discord:
https://discord.com/invite/flow
Flow CN Telegram: https://t.me/flow_zh
Flow B 站:https://space.bilibili.com/1002168058
Flow 微博:
https://weibo.com/7610419699
Flow CSDN:
https://blog.csdn.net/weixin_57551966?spm=1010.2135.3001.5343
扫码添加 Flow 官方账号微信号,加入 Flow 生态群
微信号 : FlowChainOfficial
【免责声明】市场有风险,投资需谨慎。本文不构成投资建议,用户应考虑本文中的任何意见、观点或结论是否符合其特定状况。据此投资,责任自负。