2个稳定版本
27.0.1 | 2024年2月2日 |
---|---|
27.0.0 | 2024年2月1日 |
#2333 in 魔法豆
在 2 crates 中使用
3.5MB
68K SLoC
合约模块
合约模块为运行时提供部署和执行WebAssembly智能合约的功能。
概述
此模块基于 frame_support::traits::fungible
特性扩展账户以具有智能合约功能。它可以与基于 frame_support::traits::fungible
的其他模块一起使用。这些“智能合约账户”具有实例化智能合约和调用其他合约和非合约账户的能力。
智能合约代码仅存储一次,之后可以通过其 code_hash
进行检索。这意味着可以从相同的 code
实例化多个智能合约,而不必每次都复制代码。
当调用智能合约时,其关联的代码通过代码哈希检索并执行。此调用可以更改智能合约账户的存储条目、实例化新的智能合约或调用其他智能合约。
最后,当账户被回收时,其关联的智能合约代码和存储也将被删除。
权重
发送者必须在每次调用时指定一个Weight
限制,因为智能合约调用的所有指令都需要权重。调用后未使用的权重将被退还,无论执行结果如何。
如果达到权重限制,则所有调用和状态更改(包括余额转移)仅在当前调用级别的合约中进行回滚。例如,如果合约A调用B,而B在调用过程中耗尽权重,则B的所有调用都将被回滚。假设合约A正确处理错误,A的其他调用和状态更改仍然存在。
一个ref_time
Weight
定义为在运行时的参考机器上执行时间的1皮秒。
回滚行为
合约调用失败不会级联。在子调用中发生失败时,它们不会“冒泡”,调用只会在特定的合约级别回滚。例如,如果合约A调用合约B,而B失败,A可以决定如何处理该失败,要么继续执行,要么回滚A的更改。
链下执行
通常,合约执行需要是确定的,以便所有节点在执行时得出相同的结论。为此,我们不允许任何可能导致不确定性的指令。最明显的是任何浮点运算。话虽如此,有时合约是在链下执行的,因此不受共识的约束。如果代码只由单个节点执行,并且其他行为者隐式信任,则属于此类情况。可信执行环境就是这种情况。因此,我们允许在以下约束下执行不确定性的代码以供链下使用
- 任何合约都不能从不确定的代码中实例化。执行代码的唯一方法是使用来自确定性合约的代理调用。
- 希望使用此功能的代码需要依赖于
pallet-contracts
并直接使用bare_call()
。这确保默认情况下pallet-contracts
不会暴露任何不确定性。
如何使用
不确定的代码可以通过传递Determinism::Relaxed
到upload_code()
来在链上部署。然后,如果使用bare_call()
并传递Determinism::Relaxed
给它,则确定性合约可以将其委派调用。**从链上事务调用合约时,切勿使用此参数**。
接口
可调用的函数
这些在参考文档中有记录。
暴露给合约的接口
每个合约都是一个类似这样的WebAssembly模块
(module
;; Invoked by pallet-contracts when a contract is instantiated.
;; No arguments and empty return type.
(func (export "deploy"))
;; Invoked by pallet-contracts when a contract is called.
;; No arguments and empty return type.
(func (export "call"))
;; If a contract uses memory it must be imported. Memory is optional.
;; The maximum allowed memory size depends on the pallet-contracts configuration.
(import "env" "memory" (memory 1 1))
;; This is one of many functions that can be imported and is implemented by pallet-contracts.
;; This function is used to copy the result buffer and flags back to the caller.
(import "seal0" "seal_return" (func $seal_return (param i32 i32 i32)))
)
所有可导入函数的文档可以在此处找到这里。
使用方法
本模块执行WebAssembly智能合约。这些合约可以潜在地用任何编译为Wasm的语言编写。然而,使用专门针对此模块的语言会使事情变得容易得多。其中一种语言是ink!
。它允许使用Rust编程语言编写基于WebAssembly的智能合约。
调试
当合约作为RPC调用时,可以通过debug_message
API向客户端发送消息。此API在ink!中通过ink_env::debug_message()
公开。
这些消息被收集到内部缓冲区中,并发送到RPC客户端。是否以及如何将这些消息呈现给用户取决于每个客户端。
此缓冲区也作为调试消息打印出来。为了在节点控制台看到这些消息,需要将runtime::contracts
目标的日志级别提升到至少debug
级别。然而,由于区块生成的噪音,这些消息很容易被忽略。在控制台上观察它们的良好起点是使用以下命令行在Substrate存储库的根目录下
cargo run --release -- --dev -lerror,runtime::contracts=debug
这会将runtime::contracts
的日志级别提升到debug
,并将所有其他目标提升到error
,以防止它们在控制台上垃圾邮件。
--dev
:使用dev链规范--tmp
:使用临时存储链数据(退出时删除链状态)
主机函数跟踪
对于合约作者来说,这是一个有用的调试工具,可以看到哪些主机函数被调用,使用什么参数调用,以及结果是什么。
为了在节点控制台上看到这些消息,需要将runtime::contracts::strace
目标的日志级别提升到trace
级别。
示例
cargo run --release -- --dev -lerror,runtime::contracts::strace=trace,runtime::contracts=debug
不稳定的接口
由于希望在新合约接口的开发中采用迭代方法,因此此组件包含不稳定接口的概念。类似于Rust夜间编译器,它允许我们添加新接口,但将它们标记为不稳定,以便合约语言可以在我们稳定这些接口之前进行实验并提供反馈。
为了访问在runtime.rs
中标记为#[unstable]
的接口,需要将pallet_contracts::Config::UnsafeUnstableInterface
设置为ConstU32<true>
。**显然,任何生产环境运行时都不应使用此功能:除了可能更改或删除之外,这些接口可能没有与它们相关联的正确权重,因此被认为是不可靠的**。
新接口通常以不稳定的形式添加,在提升为稳定接口之前可能需要经历多次迭代。
许可证:Apache-2.0
依赖项
~19–38MB
~648K SLoC