news 2026/9/26 2:31:56

Substrate contracts pallet 真实世界合约基准测试指南:benchmarks 目录与 ink!/solang ERC-20 宏基准剖析

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
Substrate contracts pallet 真实世界合约基准测试指南:benchmarks 目录与 ink!/solang ERC-20 宏基准剖析
  • 区块链
  • 开发框架
  • 后端

【免费下载链接】substrate

Substrate: The platform for blockchain innovators

项目地址:https://gitcode.com/gh_mirrors/su/substrate
点击查看免费下载

Substrate 的pallet-contracts(智能合约模块)在frame/contracts/benchmarks/目录中存放了两组用真实编程语言(ink! 与 Solidity/solang)编写并编译的 ERC-20 Wasm 合约。本文以该目录及其 README 为核心,详细说明这些真实世界合约在 Substrate 合约基准测试体系中的定位——它们不参与权重(weight)标定,而是专门服务于#[extra]宏基准测试,用于在大体积 Wasm 模块场景下横向比较不同合约语言与不同执行引擎的差异。读完本文,你将掌握benchmarks/目录每个文件的用途、对应基准测试的源码实现与调用链,以及如何运行这些宏基准。

一、目录概览:真实世界合约在基准测试中的角色

frame/contracts/benchmarks/目录下共包含 5 个文件:

文件说明
ink_erc20.wasm用 ink! 3.0.0-rc4 编写的 ERC-20 合约,编译产物,基准测试实际消费的文件
ink_erc20_test.wasm同合约的测试专用变体,仅在基准测试以测试模式(cfg(test))运行时被加载
ink_erc20.json对应的 ink! 合约元数据(metadata),仅作信息参考
solang_erc20.wasm用 Solidity 编写、由 Hyperledger solang 0.1.7 编译器产出的 ERC-20 合约,基准测试实际消费的文件
solang_erc20.json对应的 solang 合约元数据,仅作信息参考

根据 frame/contracts/benchmarks/README.md 的明确说明,本目录的核心定位有两点:

  1. 真实世界(real world)合约:这些不是为基准测试而手工构造的玩具 Wasm,而是分别由 ink! 和 solang 两个生态产出的真实合约代码。
  2. 用于宏基准(macro benchmarks)而非权重标定:它们的用途不是像frame/contracts/src/benchmarking/mod.rs中其余基准那样计算 extrinsic 权重,而是用来在更大的 Wasm 模块前提下比较不同的合约语言(Rust 系 ink! vs Solidity 系 solang)和执行引擎的表现差异。

同时 README 特别强调:目录中的 JSON 文件仅供信息参考,并不参与基准测试的实际执行;真正被消费的是.wasm二进制文件。

二、这些合约如何被基准测试消费:#[extra]与load_benchmark!

从源码结构看,本目录文件被src/benchmarking中标记为#[extra]的基准测试使用(见 frame/contracts/src/benchmarking/mod.rs)。Substrate 的frame-benchmarking宏体系中,#[extra]基准不会进入权重生成流程,而是作为额外补充基准存在,这与 README 中"不用于确定权重"的描述完全吻合。

加载 Wasm 文件的核心机制是load_benchmark!宏(位于 frame/contracts/src/benchmarking/mod.rs):

macro_rules! load_benchmark { ($name:expr) => {{ #[cfg(not(test))] { include_bytes!(concat!("../../benchmarks/", $name, ".wasm")) } #[cfg(test)] { include_bytes!(concat!("../../benchmarks/", $name, "_test.wasm")) } }}; }

该宏通过include_bytes!在编译期把 Wasm 二进制直接嵌入 runtime,并针对"基准测试运行"与"测试运行"两种模式选择不同文件:

  • 非测试构建:加载ink_erc20.wasm;
  • 测试构建(cfg(test)):加载ink_erc20_test.wasm。

其注释解释了原因:ink! 合约依赖的类型大小在测试环境下定义不同,因此需要一份专门针对测试环境编译的变体;而 solang 合约在这一方面约束更宽松,不需要单独的测试版本(这也解释了目录中为何没有solang_erc20_test.wasm)。

三、源码级实现:ink_erc20_transfer基准

ink_erc20_transfer是消费ink_erc20.wasm的#[extra]基准,完整实现位于 frame/contracts/src/benchmarking/mod.rs:

// Execute one erc20 transfer using the ink! erc20 example contract. #[extra] #[pov_mode = Measured] ink_erc20_transfer { let code = load_benchmark!("ink_erc20"); let data = { let new: ([u8; 4], BalanceOf<T>) = ([0x9b, 0xae, 0x9d, 0x5e], 1000u32.into()); new.encode() }; let instance = Contract::<T>::new( WasmModule::from_code(code), data, )?; let data = { let transfer: ([u8; 4], AccountIdOf<T>, BalanceOf<T>) = ( [0x84, 0xa1, 0x5d, 0xa1], account::<T::AccountId>("receiver", 0, 0), 1u32.into(), ); transfer.encode() }; }: { <Contracts<T>>::bare_call( instance.caller, instance.account_id, 0u32.into(), Weight::MAX, None, data, DebugInfo::Skip, CollectEvents::Skip, Determinism::Enforced, ) .result?; }

这段代码揭示了完整的调用链,可以与ink_erc20.json中的元数据一一对应印证:

  1. 构造函数调用:data中首 4 字节0x9b ae 9d 5e正是ink_erc20.json中new构造器的 selector,参数为初始供给1000(Balance类型,即u128)。
  2. 部署:Contract::<T>::new(...)与WasmModule::from_code(code)结合,把真实 Wasm 模块部署为链上合约。from_code的实现见 frame/contracts/src/benchmarking/code.rs,它会解析 Wasm 模块的导入段,提取线性内存的初始页数与最大页数,并计算代码哈希。
  3. 转账调用:data中0x84 a1 5d a1正是ink_erc20.json中transfer消息的 selector(0x84a15da1),携带接收方账户与转账额1。
  4. 执行:通过bare_call(不经过 extrinsics 层、绕过签名的底层调用接口)执行一次 ERC-20 转账,使用Weight::MAX的无限 Gas,并以Determinism::Enforced强制确定性执行。

从元数据(ink_erc20.json)可知,该合约还包含total_supply、balance_of、allowance、approve、transfer_from等标准 ERC-20 消息,存储布局由total_supply、balances(ink 的 stash/hashmap 结构)与allowances组成,错误类型为InsufficientBalance/InsufficientAllowance。基准选择transfer这一可变消息,正好覆盖了余额读取、存储写入与事件(Transfer)派发的完整路径。

四、源码级实现:solang_erc20_transfer基准

solang_erc20_transfer消费solang_erc20.wasm,实现位于 frame/contracts/src/benchmarking/mod.rs:

// Execute one erc20 transfer using the open zeppelin erc20 contract compiled with solang. #[extra] #[pov_mode = Measured] solang_erc20_transfer { let code = include_bytes!("../../benchmarks/solang_erc20.wasm"); let caller = account::<T::AccountId>("instantiator", 0, 0); let mut balance = [0u8; 32]; balance[0] = 100; let data = { let new: ([u8; 4], &str, &str, [u8; 32], AccountIdOf<T>) = ( [0xa6, 0xf1, 0xf5, 0xe1], "KSM", "K", balance, caller.clone(), ); new.encode() }; let instance = Contract::<T>::with_caller( caller, WasmModule::from_code(code), data, )?; balance[0] = 1; let data = { let transfer: ([u8; 4], AccountIdOf<T>, [u8; 32]) = ( [0x6a, 0x46, 0x73, 0x94], account::<T::AccountId>("receiver", 0, 0), balance, ); transfer.encode() }; }: { <Contracts<T>>::bare_call( instance.caller, instance.account_id, 0u32.into(), Weight::MAX, None, data, DebugInfo::Skip, CollectEvents::Skip, Determinism::Enforced, ) .result?; }

与 ink! 版本的关键差异点:

  • 直接include_bytes!:由于 solang 合约不需要测试变体,这里直接内嵌solang_erc20.wasm,未走load_benchmark!宏。
  • 构造参数不同:selector0xa6 f1 f5 e1对应 solang_erc20.json 中new构造器(合约名ERC20PresetFixedSupply),参数为代币名"KSM"、符号"K"、initialSupply(以 32 字节表示的 u256,这里首字节为 100)以及 owner 账户。
  • u256 语义:Solidity 的initialSupply类型为u256,对应元数据中的 32 字节编码,与 ink! 的u128形成鲜明对比。
  • transfer 参数形态:transfer的 selector 为0x6a 46 73 94,第三个参数同样以 32 字节([u8; 32])编码数量(首字节为 1),体现了两种语言 ABI 编码方式的差异。
  • 显式指定部署者:使用with_caller明确指定instantiator账户,而非默认账户。

两个基准放在一起恰好构成"同一业务(ERC-20 转账)、两种语言、两套 ABI 编码"的对照实验,这正是 README 所述"比较不同合约语言与执行引擎"的直接落地。

五、运行这些宏基准测试

#[extra]基准的运行方式与普通权重基准相同,只是需要加上--extra标志。源码注释中给出了可参考的调试命令(见 frame/contracts/src/benchmarking/mod.rs,原命令用于输出当前Schedule,同样适用于运行本目录相关基准):

cargo run \ --features runtime-benchmarks \ -- benchmark pallet --extra --dev --execution=native \ -p pallet_contracts -e print_schedule --no-median-slopes --no-min-squares

如需运行具体的宏基准,可将-e后的参数替换为目标基准名(如ink_erc20_transfer、solang_erc20_transfer),并保留--extra。需要注意:

  • 必须以--features runtime-benchmarks特性编译,因为src/benchmarking/mod.rs整体以#![cfg(feature = "runtime-benchmarks")]守卫;
  • --execution=native确保以原生 runtime 执行(print_schedule这类基准在非 std 环境下会直接返回错误);
  • 这些基准依赖pallet_balances提供账户资金(基准中通过caller_funding为调用者铸币),因此运行 runtime 需要同时配置pallet-contracts与pallet-balances。

此外,src/benchmarking末尾的impl_benchmark_test_suite!宏(frame/contracts/src/benchmarking/mod.rs)会把上述基准(含ink_erc20_transfer)挂接到测试套件,在测试模式下自动改用ink_erc20_test.wasm执行。

六、指令级基准与沙箱:宏基准之外的对照

值得补充的是,本目录的"真实合约"基准与src/benchmarking中的指令级基准(instruction benchmarks)形成互补。指令级基准(如instr_i64const)并不实例化完整合约,而是通过 frame/contracts/src/benchmarking/sandbox.rs 中的Sandbox仅实例化 Wasm 执行环境——它使用EmptyEnv(不导入任何 seal 函数)执行合约的call导出函数,并把 wasmi 引擎的燃料(fuel)设为u64::MAX,以纯粹测量单个 Wasm 指令的执行成本。

两者的分工可以概括为:

基准类别输入目的是否参与权重
权重基准(weight benchmarks)程序化生成的 Wasm 模块(WasmModule::dummy/sized)标定各 extrinsic 与 seal API 的权重是
指令级基准(instruction benchmarks)程序化生成的单指令模块生成每条 Wasm 指令的权重是
宏基准(macro benchmarks,本目录)真实世界合约(ink!/solang ERC-20)对比合约语言与执行引擎、验证大模块场景否(#[extra])

七、结论与进一步探索

frame/contracts/benchmarks/是 Substrate 合约基准测试体系中"真实世界验证"一环:它用 ink! 与 solang 两条技术栈编译出的同业务合约,通过#[extra]宏基准在完整的沙箱执行路径(部署 → 实例化 →bare_call)中运行,与程序化生成的合成基准互为印证。JSON 元数据文件则完整记录了合约 ABI、selector、存储布局与编译器信息,是理解两个基准中硬编码字节序列(如0x9bae9d5e、0x84a15da1、0xa6f1f5e1)的权威参考。

感兴趣的读者可以继续深入以下路径:

  • frame/contracts/benchmarks/ink_erc20.json:ink! ERC-20 完整 ABI 与存储布局;
  • frame/contracts/benchmarks/solang_erc20.json:solang ERC-20(ERC20PresetFixedSupply)完整 ABI;
  • frame/contracts/src/benchmarking/mod.rs:全部基准实现、load_benchmark!宏与运行命令注释;
  • frame/contracts/src/benchmarking/code.rs:WasmModule构造逻辑(from_code、dummy、sized);
  • frame/contracts/src/benchmarking/sandbox.rs:指令级基准的无导入沙箱环境。
  • 区块链
  • 开发框架
  • 后端

【免费下载链接】substrate

Substrate: The platform for blockchain innovators

项目地址:https://gitcode.com/gh_mirrors/su/substrate
点击查看免费下载

相关推荐

上一篇:Obsidian文档搜索插件终极指南:快速索引与智能过滤技巧
下一篇:Cloudflare Computer 的 Prose 写作规范:从代码注释到提交信息的完整指南

创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考

版权声明: 本文来自互联网用户投稿,该文观点仅代表作者本人,不代表本站立场。本站仅提供信息存储空间服务,不拥有所有权,不承担相关法律责任。如若内容造成侵权/违法违规/事实不符,请联系邮箱:809451989@qq.com进行投诉反馈,一经查实,立即删除!
网站建设 2026/9/26 2:31:35

镜像导航站与镜像源配置实战:从概念到主流工具加速指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华
网站建设 2026/9/26 2:30:55

Bangumi 追番记录应用:从零到日常使用的完整指南

Bangumi 追番记录应用&#xff1a;从零到日常使用的完整指南 【免费下载链接】Bangumi :electron: An unofficial https://bgm.tv ui first app client for Android and iOS, built with React Native. 一个无广告、以爱好为驱动、不以盈利为目的、专门做 ACG 的类似豆瓣的追番…

作者头像 李华
网站建设 2026/9/26 2:29:14

JS固定电话正则从入门到踩坑:座机号校验的完整实践

最近在维护一个订单系统&#xff0c;表单里"联系电话"字段被用户投诉了好几轮。明明填了"0755-12345678"这种标准座机号&#xff0c;系统却提示格式错误&#xff1b;而"123-12345678"这种明显不合理的号码反倒能提交成功。定位半天&#xff0c;问…

作者头像 李华
网站建设 2026/9/26 2:28:44

个人能力操作系统:技能原子+认知链实战指南

1. 项目概述&#xff1a;这不是一个“技能管理后台”&#xff0c;而是一套可落地的个人能力操作系统“Get---创建并修改Skill以及认知总结”这个标题初看像某个内部系统界面的按钮文案&#xff0c;但拆开来看&#xff0c;“Get”不是动词“获取”&#xff0c;而是隐喻“掌握、内…

作者头像 李华
网站建设 2026/9/26 2:28:27

网盘直链下载助手使用指南:LinkSwift 安装与直链获取全流程

网盘直链下载助手使用指南&#xff1a;LinkSwift 安装与直链获取全流程 【免费下载链接】Online-disk-direct-link-download-assistant 一个基于 JavaScript 的网盘文件下载地址获取工具。基于【网盘直链下载助手】修改 &#xff0c;支持 百度网盘 / 阿里云盘 / 中国移动云盘 /…

作者头像 李华
网站建设 2026/9/26 2:27:32

内容合规与选题策略:从技术实践到生活技巧的博客创作指南

/* MD / 富文本中的 .toc(含博客园搬家等嵌套结构);.toc-box 在侧栏,不受影响 */#content_views .toc,/* 编辑器常在目录前后插入空 p(:empty 仍占 20px),一并去掉避免顶空隙 */#content_views.markdown_views > p:empty:has(+ .toc),#content_views.markdown_views …

作者头像 李华