首页
/ fuels-ts 实战指南:在部署 Sway 合约时设置 Configurable Constants(可配置常量)

fuels-ts 实战指南:在部署 Sway 合约时设置 Configurable Constants(可配置常量)

2026-09-05 22:16:59作者:余洋婵Anita

本篇基于 fuels-ts 文档《Configurable Constants》展开,讲解 Fuel SDK 中 Sway 合约"可配置常量"(configurable constants)的完整用法:如何在合约中用 configurable 块声明带默认值的常量,如何在部署时通过 configurableConstants 选项按需覆盖其中任意常量,以及配置不完整(例如 Struct 缺字段)时的报错行为。读完本文,你将掌握可配置常量的声明、覆盖与验证全流程,并理解 SDK 在底层"改写字节码"的实现原理。

一、什么是 Configurable Constants

Sway 提供了强大的可配置常量特性:在创建合约时,可以定义一批常量并为每个常量指定默认值;在合约部署之前,你可以重新定义这些常量的值——可以只改其中一部分,也可以全部覆盖。

这一特性为动态的合约环境提供了灵活性:同一份合约代码可以在不同环境下以不同的常量配置部署,实现高定制化,从而编写出更高效、更易适应不同场景的智能合约。

二、在 Sway 合约中声明可配置常量

下面是一个声明了四个可配置常量的示例合约(来自仓库文档配套 Sway 工程 echo-configurables):

contract;

enum MyEnum {
    Checked: (),
    Pending: (),
}

struct MyStruct {
    x: u8,
    y: u8,
    state: MyEnum,
}

configurable {
    age: u8 = 25,
    tag: str[4] = __to_str_array("fuel"),
    grades: [u8; 4] = [3, 4, 3, 2],
    my_struct: MyStruct = MyStruct {
        x: 1,
        y: 2,
        state: MyEnum::Pending,
    },
}

abi EchoConfigurables {
    fn echo_configurables() -> (u8, str[4], [u8; 4], MyStruct);
}

impl EchoConfigurables for Contract {
    fn echo_configurables() -> (u8, str[4], [u8; 4], MyStruct) {
        (age, tag, grades, my_struct)
    }
}

该合约中,echo_configurables 函数会返回四个可配置常量的当前值,供我们用它来演示通过 SDK 设置常量配置。示例覆盖了多种典型类型:无符号整数(u8)、定长字符串(str[4])、固定长度数组([u8; 4])以及嵌套了枚举的 Struct

三、部署时为新值覆盖常量

在合约部署阶段,可以为任意一个或全部可配置常量指定新值。下面的示例(对应 文档代码片段)只覆盖了 age 一个常量,其余常量保持 Sway 中定义的默认值:

import { Provider, Wallet } from 'fuels';

import { LOCAL_NETWORK_URL, WALLET_PVT_KEY } from '../../../env';
import { EchoConfigurablesFactory } from '../../../typegend';

const provider = new Provider(LOCAL_NETWORK_URL);
const wallet = Wallet.fromPrivateKey(WALLET_PVT_KEY, provider);

const configurableConstants = {
  age: 10,
};

const deploy = await EchoConfigurablesFactory.deploy(wallet, {
  configurableConstants,
});
const { contract } = await deploy.waitForResult();

const {
  value: [age, tag, grades, myStruct],
} = await contract.functions.echo_configurables().get();

// age got updated
console.log('age', age); // 10
// while the rest are default values
console.log('tag', tag); // 'fuel'
console.log('grades', grades); // [3, 4, 3, 2]
console.log('myStruct', myStruct); // { x: 1, y: 2, state: 'Pending' }

要点说明:

  • EchoConfigurablesFactory 由 fuels-ts 的类型生成(typegen)流程基于合约 ABI 生成,deploy 方法接收的第二个参数即 DeployContractOptions
  • configurableConstants 是一个以"常量名"为键的对象,类型签名为 { [name: string]: unknown }只需给出你想覆盖的常量,未提及的常量自动沿用 Sway 源码中的默认值;
  • 调用 deploy(wallet, { configurableConstants }) 后等待交易结果拿到 contract 实例,再通过 contract.functions.echo_configurables().get() 验证:age 变为 10,而 taggradesmyStruct 仍为 'fuel'[3, 4, 3, 2]{ x: 1, y: 2, state: 'Pending' }

四、Struct 常量必须完整配置,否则部署报错

文档特别强调:为 Struct 类型常量赋新值时,必须定义该 Struct 的全部属性,否则会抛出错误。仓库文档片段中给出了反例:

const invalidConfigurables = {
  my_struct: {
    x: 10,
  },
};
try {
  await EchoConfigurablesFactory.deploy(wallet, {
    configurableConstants: invalidConfigurables,
  });
} catch (e) {
  console.log('error', e);
  // error: Error setting configurable constants on contract:
  // Invalid struct MyStruct. Field "y" not present.
}

只写了 x: 10 而遗漏了 ystate 字段,deploy 会同步抛出 Error setting configurable constants on contract: Invalid struct MyStruct. Field "y" not present.。该错误信息恰好对应 ContractFactory.setConfigurableConstants 中统一的错误包装逻辑(见下文原理分析)。

五、源码级原理:常量值是如何"写进"合约的

从源码结构看,可配置常量的本质是在部署交易发出之前,把编码后的常量值直接覆写到合约字节码的固定偏移位置,即对字节码做"打补丁"。关键调用链如下:

  1. 入口ContractFactory.deploydeployAsCreateTxprepareDeploy。在 prepareDeploy 中,只要 deployOptions.configurableConstants 存在,就会先调用 this.setConfigurableConstants(configurableConstants)之后才创建交易请求。由于合约 ID(contractId)是在字节码改写之后基于 bytecode + salt + stateRoot 计算的(见 createTransactionRequestgetContractId(bytecode, options.salt, stateRoot) 调用),可以推断:不同 configurableConstants 配置会产生不同的字节码与不同的合约 ID。

  2. 核心逻辑setConfigurableConstants 逐条处理用户传入的键值对:

    • 先校验合约 ABI 中确实声明了 configurables,否则抛出 Contract does not have configurables to be set
    • 再校验每个键都在 this.interface.configurables 中存在,否则抛出 Contract does not have a configurable named: '${key}'
    • 随后通过 Interface.encodeConfigurable 按 ABI 中的 configurableType 将 JS 值编码为字节序列,并从 this.interface.configurables[key].offset 取出该常量在字节码中的偏移地址,执行 bytes.set(encoded, offset) 完成覆写,最后把改写后的字节序列回写到 this.bytecode
    • 所有异常都会被捕获并统一包装为 INVALID_CONFIGURABLE_CONSTANTS 错误,消息前缀即文档示例中看到的 Error setting configurable constants on contract: ...,Struct 缺字段时内部抛出 Invalid struct MyStruct. Field "y" not present. 后同样走这条包装路径。
  3. ABI 侧支撑Interface 构造函数 在初始化时就把 JSON ABI 中的 configurables 数组转成以名称为键的映射,每条记录包含常量名、类型与 offset,这正是部署时能"定位到字节码哪一段"的依据。

  4. Blob 分片部署同样支持:当合约超过链上 contractMaxSize 限制时,deploy 会自动走 deployAsBlobTx 分片路径;该方法在分块之前同样会调用 setConfigurableConstants,因此无论走 Create 交易还是 Blob 分片部署,configurableConstants 都能生效。

六、测试验证:SDK 支持的可配置常量类型

仓库集成测试 configurable-contract.test.tsConfigurableContractFactory 系统性地验证了各类型常量的默认值断言与覆盖能力,可作为"哪些类型可以安全配置"的权威参考。测试中定义的默认值与覆盖用例涵盖:

类型 默认值 覆盖值示例
U8 / U16 / U32 / U64 10 / 301 / 799 / 100000 99 / 499 / 854 / 999999
BOOL true false
B256 0x1d6ebd57... 随机 256 位值
ENUM 'red' 'blue'(以字符串传枚举变体名)
ARRAY(二维数组) [[253,254],[255,256]] [[666,667],[656,657]]
STR_4(定长字符串) 'fuel' 'leuf'
TUPLE [12, false, 'hi'] [99, true, 'by']
STRUCT_1 { tag:'000', age:21, scores:[1,3,4] } { tag:'007', age:30, scores:[10,10,10] }

测试的部署方式值得注意:它并没有手动 deploy,而是使用 fuels/test-utils 提供的 launchTestNode,通过 contractsConfigs 参数把工厂与选项一并交给测试节点:

function setupContract(configurableConstants?: { [name: string]: unknown }) {
  return launchTestNode({
    contractsConfigs: [
      {
        factory: ConfigurableContractFactory,
        options: { configurableConstants },
      },
    ],
  });
}

这说明 configurableConstants 作为 DeployContractOptions 的一部分,同样适用于测试节点批量部署场景。该测试文件同时标注了 @group node@group browser,即同一套用法在 Node 与浏览器环境下均已验证。此外,仓库中还存在 predicate-configurables.test.ts 等用例,表明该机制不只服务于合约部署。

七、一个真实应用场景:SDK CLI 部署代理合约

fuels-ts 的 CLI 部署命令内部就依赖了 configurableConstants。在 deployContracts.ts 中,部署 SR-C14 兼容的代理合约(Proxy Contract)时,SDK 会把目标合约 ID 与部署者地址写入代理合约的两个可配置常量:

const proxyDeployConfig: DeployContractOptions = {
  ...commonDeployConfig,
  storageSlots: mergedStorageSlots,
  configurableConstants: {
    INITIAL_TARGET: { bits: targetContract.id.toB256() },
    INITIAL_OWNER: { Initialized: { Address: { bits: wallet.address.toB256() } } },
  },
};

示例体现了两个实践细节:b256 类常量需以 { bits: ... } 的包装结构传入,枚举型常量则使用 { 变体名: { ... } } 的 tagged union 结构——这与 AbiCoder 的编码规则保持一致。

八、小结与注意事项

  1. 只能覆盖、不能新增configurableConstants 的键必须存在于 ABI 声明的 configurables 中,且合约必须至少声明一个可配置常量,否则 SDK 直接抛错;
  2. Struct 必须完整:覆盖 Struct 常量时缺一不可字段,否则报错 Invalid struct Xxx. Field "yyy" not present.
  3. 时机是部署时:从源码实现看,常量覆盖发生在 deploy 创建交易请求之前,属于"部署时一次性写入字节码"的语义,并非部署后可通过链上调用的存储写入;
  4. 影响合约 ID:常量值被覆写进字节码后才计算 contractId,因此同一份合约代码配不同的 configurableConstants,得到的合约 ID 不同;
  5. 全类型支持:u8/u16/u32/u64、bool、b256、enum、数组、定长字符串、元组、struct 等类型均有集成测试覆盖,可放心使用。
登录后查看全文
热门项目推荐
相关项目推荐