fuels-rs 中配置可配置常量(Configurable Constants):Sway 部署期参数覆盖完整指南
fuels-rs 中配置可配置常量Configurable ConstantsSway 部署期参数覆盖完整指南【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs在 Fuel 链上部署合约或脚本时Sway 的configurable常量允许开发者在部署期改变程序字节码中内嵌的默认值而无需重新编译链上代码。fuels-rsFuel Network Rust SDK会为每一个 configurable 生成类型安全的with_XXXbuilder 方法配合abigen!与Contract::load_from/deploy调用链即可实现“一套字节码、多次参数化部署”。本文基于 configurable-constants.md 展开结合仓库中的 Sway 示例合约、端到端测试与代码生成源码完整讲解从 Sway 声明到 Rust 侧覆盖、部署再到链上验证的整个流程。一、什么是 configurable 常量在 Sway 智能合约contract、脚本script与谓词predicate中都可以通过configurable块声明一组常量。这些常量在编译时拥有默认值但它们的存放位置字节码内的 offset会被编译器记录在 ABI 中使得 SDK 能在部署/提交前对字节码做定点修补patching。仓库中用于演示的合约位于 e2e/sway/contracts/configurables/src/main.sw其声明的常量几乎覆盖了 Sway 的主流类型contract; #[allow(dead_code)] enum EnumWithGenericD { VariantOne: D, VariantTwo: (), } struct StructWithGenericD { field_1: D, field_2: u64, } configurable { BOOL: bool true, U8: u8 8, U16: u16 16, U32: u32 32, U64: u64 63, U256: u256 0x0000000000000000000000000000000000000000000000000000000000000008u256, B256: b256 0x0101010101010101010101010101010101010101010101010101010101010101, STR_4: str[4] __to_str_array(fuel), TUPLE: (u8, bool) (8, true), ARRAY: [u32; 3] [253, 254, 255], STRUCT: StructWithGenericu8 StructWithGeneric { field_1: 8, field_2: 16, }, ENUM: EnumWithGenericbool EnumWithGeneric::VariantOne(true), }该合约还暴露了一个只读方法return_configurables()用于把全部常量打包成一个元组返回方便链上验证当前实际生效的配置见下文第五节。与此平行的脚本示例位于 e2e/sway/scripts/script_configurables/src/main.sw其configurable声明与合约版完全一致仅将abi换成了直接返回元组的fn main()。上表覆盖的类型可归纳为常量Sway 类型默认值Rust 侧对应类型BOOLbooltrueboolU8u88u8U16u1616u16U32u3232u32U64u6463u64U256u2560x…08U256B256b2560x01…01Bits256STR_4str[4]fuelSizedAsciiString4TUPLE(u8, bool)(8, true)(u8, bool)ARRAY[u32; 3][253, 254, 255][u32; 3]STRUCTStructWithGenericu8field_1: 8, field_2: 16同名生成结构体ENUMEnumWithGenericboolVariantOne(true)同名生成枚举二、abigen! 如何为 configurable 生成 Rust 代码在 Rust 侧调用abigen!时代码生成器会同时读取 ABI 中的configurables信息。从 bindings/contract.rs 可以看到生成逻辑会将合约名与Configurables拼成配置结构体名let configuration_struct_name ident(format!({name}Configurables)); let constant_configuration_code generate_code_for_configurable_constants(configuration_struct_name, abi.configurables)?;也就是说合约MyContract对应生成MyContractConfigurables脚本MyScript对应生成MyScriptConfigurables脚本绑定见 bindings/script.rs。生成逻辑集中在 abigen/configurables.rs其关键行为是每个 configurable 生成一个专用with_方法。方法名由format!(with_{}, configurable.name)产生例如STR_4→with_STR_4。该值保留了原常量名的大小写并通过#[allow(non_snake_case)]抑制 lint 警告。方法接受与 Sway 声明完全一致的 Rust 类型类型由 ABI 中的type_application解析而来支持结构体、枚举、元组、数组、字符串乃至泛型实例等复杂类型。方法把值编码并记录下来。生成的伪代码如下pub fn with_STR_4(mut self, value: SizedAsciiString4) - fuels::prelude::ResultSelf { let encoded self.encoder.encode([ SizedAsciiString4 as fuels::core::traits::Tokenizable::into_token(value) ])?; self.offsets_with_data.push(fuels::core::Configurable { offset: ABI 中记录的字节偏移, data: encoded, }); Ok(self) }生成的配置结构体本身由三部分组成#[derive(Clone, Debug, Default)] pub struct MyContractConfigurables { offsets_with_data: Vec::fuels::core::Configurable, encoder: ::fuels::core::codec::ABIEncoder, }并附带FromMyContractConfigurables for fuels::core::Configurables与From… for Vecfuels::core::Configurable转换实现使其可以直接塞进部署配置。注意每次调用with_XXX都会把“新值”加入offsets_with_data列表因此该方法消费并返回self天然支持链式调用对同一个常量重复调用时后者会追加在列表末尾。三、编码后的数据如何写回字节码with_XXX只负责编码与记录真正改写字节码的是Configurables类型。packages/fuels-core/src/lib.rs 中定义了底层数据结构#[derive(Debug, Clone, Default, PartialEq)] pub struct Configurable { /// 数据在二进制中的偏移量单位字节 pub offset: u64, /// 与该 configurable 对应的已编码数据 pub data: Vecu8, } #[derive(Debug, Clone, Default, PartialEq)] pub struct Configurables { pub offsets_with_data: VecConfigurable, }其核心方法是update_constants_in逻辑极其直接读取每个Configurable.offset把data整段覆盖写回程序二进制对应位置pub fn update_constants_in(self, binary: mut [u8]) { for c in self.offsets_with_data { let offset c.offset as usize; binary[offset..offset c.data.len()].copy_from_slice(c.data) } }这样部署上链的就不再是 ABI 里那份“默认值字节码”而是打过补丁的新版本。Configurables还提供with_shifted_offsets(shift)当同一份字节码被塞进 loader/包装结构、常量相对偏移整体平移时可对全部 offset 统一加减修正。四、在部署合约时覆盖默认值结合 e2e/tests/configurables.rs 中contract_configurables测试的完整写法覆盖流程分三步。4.1 生成绑定并准备新值abigen!(Contract( name MyContract, abi e2e/sway/contracts/configurables/out/release/configurables-abi.json )); let wallet launch_provider_and_get_wallet().await?; let str_4: SizedAsciiString4 FUEL.try_into()?; let new_struct StructWithGeneric { field_1: 16u8, field_2: 32, }; let new_enum EnumWithGeneric::VariantTwo;其中SizedAsciiString4只能容纳恰好 4 个 ASCII 字符长度不符会在try_into()时返回错误结构体与枚举则使用 abigen 生成的同名类型。4.2 链式设置 configurablelet configurables MyContractConfigurables::default() .with_BOOL(false)? .with_U8(7)? .with_U16(15)? .with_U32(31)? .with_U64(63)? .with_U256(U256::from(8))? .with_B256(Bits256([2; 32]))? .with_STR_4(str_4.clone())? .with_TUPLE((7, false))? .with_ARRAY([252, 253, 254])? .with_STRUCT(new_struct.clone())? .with_ENUM(new_enum.clone())?;每个with_XXX都返回ResultSelf因此需要?它们会推进同一个 ABI 编码器方法内任何编码失败如超出编码器限制都会在此时报错而不是拖到部署阶段。只调用其中某几个也是允许的未覆盖的常量继续沿用 Sway 源码中的默认值。4.3 注入配置并部署let contract_id Contract::load_from( sway/contracts/configurables/out/release/configurables.bin, LoadConfiguration::default().with_configurables(configurables), )? .deploy_if_not_exists(wallet, TxPolicies::default()) .await? .contract_id; let contract_instance MyContract::new(contract_id, wallet.clone());LoadConfiguration定义于 packages/fuels-programs/src/contract/regular.rs聚合了部署所需的全部可选参数storage存储槽、configurables与salt。通过with_configurables(configurables)传入的MyContractConfigurables会先经IntoConfigurables转换再随字节码一起进入Contract内部。从源码看Contract::load_from最终调用Regular::new(binary, config.configurables)把字节码与配置绑定在同一种code_types::Regular中只有当真正读取代码如生成部署交易时才会执行update_constants_in从而避免“拿到裸字节码却忘了打补丁”这类隐患。仓库在code_types模块上的注释也明确写道将 code 设为私有字段正是为了杜绝绕过 configurable 直接取用原始代码的误操作。若不需要预编译产物路径也可以用deploy系列 API把configurables构建好后经deploy_if_not_exists/deploy提交即可二者对 configurable 的处理是同一套机制。例如同文件中的contract_manual_configurables测试展示了先Contract::load_from(..., LoadConfiguration::default())再.with_configurables(configurables)的等价写法。五、链上验证读取实际生效的值部署只是写入是否生效还需要链上证据。演示合约的return_configurables()会把全部 12 个常量打包返回测试随后逐一断言let response contract_instance .methods() .return_configurables() .call() .await?; let expected_value ( false, // BOOL 被覆盖为 false 7, // U8 被覆盖为 7 15, // U16 被覆盖为 15 31, // U32 被覆盖为 31 63, // U64 保持默认 63 U256::from(8), // U256 保持默认 Bits256([2; 32]), // B256 被覆盖 str_4, // STR_4 被覆盖为 FUEL (7, false), // TUPLE 被覆盖 [252, 253, 254], // ARRAY 被覆盖 new_struct, // STRUCT 被覆盖 new_enum, // ENUM 被覆盖 ); assert_eq!(response.value, expected_value);同一目录下还有contract_default_configurables测试它完全不调用任何with_XXX直接以默认配置部署期望值则全部等于 Sway 源码中的默认值true, 8, 16, 32, 63, U256::from(8), Bits256([1; 32]), fuel, (8, true), [253, 254, 255], …。这组对照测试恰好说明不覆盖则行为完全由 Sway 默认值决定覆盖后则按新值写回双向验证了机制的正确性。六、脚本Script与谓词Predicate中的 configurableconfigurable 并不局限于合约部署。abigen!(Script(...))会为脚本生成MyScriptConfigurables调用侧流程见 e2e/tests/configurables.rs 中script_configurables测试abigen!(Script( name MyScript, abi e2e/sway/scripts/script_configurables/out/release/script_configurables-abi.json )); let wallet launch_provider_and_get_wallet().await?; let bin_path sway/scripts/script_configurables/out/release/script_configurables.bin; let instance MyScript::new(wallet, bin_path); // ...准备与合约版相同的新值... let configurables MyScriptConfigurables::new(EncoderConfig { max_tokens: 5, ..Default::default() }) .with_BOOL(false)? // ...其余 with_XXX 链式调用... .with_ENUM(new_enum.clone())?; let response instance .with_configurables(configurables) .main() .call() .await?;与合约分支的两个差异值得注意脚本配置结构体用MyScriptConfigurables::new(EncoderConfig { … })构造而非::default()。因为脚本通常要被打包进 loader 再执行如script_default_configurables测试中的convert_into_loader()编码器参数更常需要显式控制。调用点为instance.with_configurables(configurables)绑定代码会在内部调用Executable::from_bytes(binary).with_configurables(...)见 bindings/script.rs。当脚本被嵌入 loader 时常量 offset 会整体平移此时正是Configurables::with_shifted_offsets发挥作用的场景——绑定生成代码在convert_into_loader等路径中会使用平移后的偏移量重新定位。谓词predicate的代码生成同样接入了generate_code_for_configurable_constants见 abigen/bindings/predicate.rs因此“用新常量构建谓词”的用法与脚本一致。若需为谓词/脚本源码配套的预编译二进制可参考 deploying/the-fuelvm-binary-file.md 与 preuploading-code.md 中关于构建与预上传流程的说明。七、EncoderConfig编码器上限与报错排查with_XXX在编码阶段就会受EncoderConfig约束。ABIEncoderpackages/fuels-core/src/codec/abi_encoder.rs内置bounded_encoderbounded_encoder.rs通过max_depth最大嵌套深度与max_tokens最大 token 数两个计数器防止恶意/超限数据造成资源滥用。仓库中的configurable_encoder_config_is_applied测试精确演示了这一点当用默认MyScriptConfigurables::default()设置一个结构体常量时一切正常而一旦换成EncoderConfig { max_tokens: 1, ..Default::default() }同一个with_STRUCT调用会立刻报错错误信息中包含token limit 1 reached while encoding. Try increasing it由此可以总结两条工程经验默认配置足够覆盖绝大多数常规类型仅当 configurable 涉及深嵌套的复杂泛型/大体积数据时才需要调大max_tokens/max_depth。编码错误发生在with_XXX调用点而非部署时便于在测试阶段尽早暴露配置问题。八、使用要点小结Sway 侧在configurable { NAME: type default, … }中声明编译出的二进制与 ABI含各常量 offset是 SDK 修补的基础u128目前在示例中被注释并标注了上游 Sway issue 的 TODO使用时需注意版本对u128configurable 的支持情况。Rust 侧abigen!依据合约/脚本/谓词名生成NameConfigurables其中每个with_XXX均与源码同名同类型天然具备编译期类型检查。注入时机合约走LoadConfiguration::with_configurables或Contract::load_from(...).with_configurables(...)脚本/谓词走实例的.with_configurables(...)实际写回发生在生成部署交易、读取字节码的那一刻通过Configurables::update_constants_in按 offset 覆盖。验证闭环让链上方法把常量原样返回与构造端期望值做assert_eq!即可确认覆盖生效、默认值路径未被破坏。更多相关材料可继续阅读deploying/index.md合约部署总览、deploying/storage-slots.md与存储槽并存的其他部署期配置、configurables.rs含默认值/覆盖值/脚本/编码器限制四组端到端测试、configurables.rs代码生成实现 以及 packages/fuels-core/src/lib.rs 中的Configurable/Configurables底层类型。【免费下载链接】fuels-rsFuel Network Rust SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-rs创作声明:本文部分内容由AI辅助生成(AIGC),仅供参考