news 2026/9/11 14:16:10

fuels-ts 如何手动部署 SRC14 代理合约并升级合约目标

作者头像

张小明

前端开发工程师

1.2k 24
文章封面图
fuels-ts 如何手动部署 SRC14 代理合约并升级合约目标

fuels-ts 如何手动部署 SRC14 代理合约并升级合约目标

【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts

在 fuels-ts(Fuel Network TypeScript SDK)中,如果你希望合约可以后续升级、且对外暴露的地址保持不变,需要把合约部署在一个代理合约后面:先部署真实合约,再部署一个 SRC14 兼容的 owned 代理合约并把目标指向该合约,之后所有调用都通过代理合约 ID 进行;升级时只需部署新版合约并更新代理的目标。fuels包导出了Src14OwnedProxySrc14OwnedProxyFactory,就是官方推荐使用的 SRC14 合规 owned 代理合约的 TypeScript 实现(与fuels deploy命令内部使用的代理一致)。

官方文档也提示:自动化场景下更推荐直接用fuels deploy命令,它会替你完成部署与升级的全部流程;下面的手动流程适合你想自己控制每一步、或需要定制部署逻辑的情况。完整来源见 Proxy Contracts 指南 和 Deploying Contracts 指南。

准备条件

  1. 项目中安装fuels包(版本以 版本页 为准,安装命令为pnpm add fuels@<fuels版本号>)。
  2. 编写 Sway 合约并构建产物:运行forc build,或推荐通过 Fuels CLI 运行fuels buildfuels build提供端到端的类型支持)。
  3. 用 Typegen 为合约生成 TypeScript 类型:
pnpm fuels typegen -i ./abis/*-abi.json -o ./types

-i后面是合约构建后生成的、以-abi.json结尾的 ABI 文件路径;-o是生成类型的输出目录,合约类型默认就会生成(--contract是默认值,可省略)。

  1. 需要一个可连接的 Fuel 节点地址和一个部署者钱包的私钥,用于创建ProviderWallet

文档示例使用一个计数器合约演示整个流程。初始版本(v1)只有一个counter存储槽:

contract; abi Counter { #[storage(read)] fn get_count() -> u64; #[storage(write, read)] fn increment_count(amount: u64) -> u64; #[storage(write, read)] fn decrement_count(amount: u64) -> u64; } storage { counter: u64 = 0, }

后续升级版本(v2)新增了increments存储槽和get_increments()方法,用来模拟“新版本合约多了一个存储槽”的升级场景。两个完整 Sway 文件分别在 counter 和 counter-v2,两者都要构建并各自生成类型(示例中的CounterCounterFactoryCounterV2CounterV2Factory就来自这一步的产物)。

第一步:部署被代理的真实合约

使用 Typegen 生成的CounterFactory部署合约:

import { Provider, Wallet } from 'fuels'; import { CounterFactory } from './types'; // fuels typegen 的输出目录 const provider = new Provider('<本地节点URL>'); const wallet = Wallet.fromPrivateKey('<部署者钱包私钥>', provider); const counterContractFactory = new CounterFactory(wallet); const deploy = await counterContractFactory.deploy(); const { contract: counterContract } = await deploy.waitForResult();

上面两处<本地节点URL><部署者钱包私钥>需要替换为你自己的值。deploy方法会按合约大小自动选择部署方式(小合约用单个 create 交易,大合约自动拆成 blob 分块),提交交易后即返回contractIdwaitForTransactionIdwaitForResultwaitForResult()解析后才会拿到contract实例。注意如果用 blob 方式部署大合约,需要多个交易且每个交易都要等待出块,部署耗时会明显变长。

第二步:部署 SRC14 代理合约并初始化目标

部署代理时有两个必须传的参数:

  • storageSlots:要把真实合约的存储槽和Src14OwnedProxy自身的存储槽合并后传给代理,以便在部署时初始化全部存储槽;
  • configurableConstants:这是该 SRC14 合约特有的可配置常量,部署时必须传入,之后再调用initialize_proxy完成代理初始化。INITIAL_TARGET指向第一步部署的合约 ID,INITIAL_OWNER是代理的所有者地址(文档用的是部署者钱包地址)。
import { Src14OwnedProxy, Src14OwnedProxyFactory } from 'fuels'; /** * 必须把全部存储槽传给代理,以完成存储槽初始化 */ const storageSlots = counterContractFactory.storageSlots.concat( Src14OwnedProxy.storageSlots ); /** * 这些可配置常量是文档推荐的 SRC14 合规合约特有的: * 部署时必须传入,之后还要调用 `initialize_proxy` 完成代理初始化 */ const configurableConstants = { INITIAL_TARGET: { bits: counterContract.id.toB256() }, INITIAL_OWNER: { Initialized: { Address: { bits: wallet.address.toB256() } }, }, }; const proxyContractFactory = new Src14OwnedProxyFactory(wallet); const proxyDeploy = await proxyContractFactory.deploy({ storageSlots, configurableConstants, }); const { contract: proxyContract } = await proxyDeploy.waitForResult(); const { waitForResult } = await proxyContract.functions .initialize_proxy() .call(); await waitForResult();

initialize_proxy()调用完成并waitForResult解析后,代理合约才算部署并初始化完毕。

第三步:通过代理合约 ID 调用合约

关键约束:实例化合约时只使用代理合约的 ID。代理 ID 在后续升级中保持不变,这样升级后调用方代码无需改动:

import { Counter } from './types'; /** * 实例化时只用代理合约 ID: * 即使未来升级,它也是静态不变的 */ const proxiedContract = new Counter(proxyContract.id, wallet); const incrementCall = await proxiedContract.functions.increment_count(1).call(); await incrementCall.waitForResult(); const { value: count } = await proxiedContract.functions.get_count().get(); console.log('count:', count.toNumber() === 1);

文档示例的输出是count: true,表示通过代理调用increment_count(1)后读到的计数为 1(示例结果,供对照格式,不是固定数值承诺)。

第四步:部署 v2 并升级代理目标

把合约改成 v2(新增increments存储槽和get_increments()方法)后,升级分两步:部署新版合约,再调用代理的set_proxy_target把目标指向新合约 ID:

import { CounterV2, CounterV2Factory } from './types'; const deployV2 = await CounterV2Factory.deploy(wallet); const { contract: contractV2 } = await deployV2.waitForResult(); const updateTargetCall = await proxyContract.functions .set_proxy_target({ bits: contractV2.id.toB256() }) .call(); await updateTargetCall.waitForResult();

然后仍用同一个代理合约 ID、换成 v2 的合约类重新实例化,验证升级后的新方法可用:

/** * 仍然用同一个代理 ID 实例化, * 只是换成了新的合约类 */ const upgradedContract = new CounterV2(proxyContract.id, wallet); const incrementCall2 = await upgradedContract.functions .increment_count(1) .call(); await incrementCall2.waitForResult(); const { value: increments } = await upgradedContract.functions .get_increments() .get(); const { value: count2 } = await upgradedContract.functions.get_count().get(); console.log('secondCount', count2.toNumber() === 2); console.log('increments', increments);

文档示例输出为secondCount trueincrements的值:count2为 2 说明代理在目标切换后仍保留原有counter存储(升级前累计 1,升级后再 +1 变成 2),get_increments()能读到值说明 v2 新增的存储槽工作正常。

限制与已知问题

  • 新增存储槽必须先初始化:当新版本合约新增了存储槽时,这些槽必须先通过代理合约写入(初始化),之后才能被读取,否则交易会 revert。文档建议的做法是在代理合约上先对新存储槽执行一次写入。
  • 存储槽必须完整传递:部署代理时漏传任何存储槽都会导致初始化不完整,counterContractFactory.storageSlots.concat(Src14OwnedProxy.storageSlots)这一步不能省略。
  • 本文流程是手动实现的参考路径;文档明确建议,如果不需要自己控制每一步,直接用fuels deploy命令部署和升级合约,它会用同样的 SRC14 代理自动处理全部工作(见 fuels CLI 命令文档)。
  • 大合约以 blob 方式部署时耗时更长,每个 blob 交易都要等待出块后再执行下一步,部署与可交互之间的等待时间要相应放大。

参考文件

  • 手动部署与升级代理合约指南
  • 合约部署指南
  • typegen 示例代码
  • 生成类型文档

【免费下载链接】fuels-tsFuel Network Typescript SDK项目地址: https://gitcode.com/GitHub_Trending/fu/fuels-ts

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

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

HDFS元数据丢失分析与高可用架构实践

1. NameNode空载状态下的HDFS行为分析当HDFS集群的NameNode没有任何元数据时&#xff0c;整个文件系统会进入一种特殊的状态。想象一下图书馆的目录卡片全部丢失的场景——虽然书籍仍然在书架上&#xff08;DataNode上的数据块物理存在&#xff09;&#xff0c;但管理员无法定位…

作者头像 李华
网站建设 2026/9/11 14:14:46

提示工程项目风险管理:从失败项目复盘到落地清单

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

作者头像 李华
网站建设 2026/9/11 14:13:39

语音识别模组停产替代实战指南:AFE校准与平滑迁移

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

作者头像 李华
网站建设 2026/9/11 14:12:05

从神经元到世界模型:大模型全栈构建操作手册

1. 这不是一本“讲大模型”的书&#xff0c;而是一本“造大模型”的操作手册“从神经元写到世界模型”——光看标题&#xff0c;很多人第一反应是&#xff1a;又一本讲Transformer、讲LLaMA、讲RLHF的科普读物&#xff1f;不。这本书的底层逻辑根本不在“解释”&#xff0c;而在…

作者头像 李华
网站建设 2026/9/11 14:11:42

7款AI论文写作工具评测与查重优化技巧

1. 为什么需要AI论文写作辅助工具&#xff1f; 作为一名科研工作者&#xff0c;我深刻理解论文写作过程中的痛点。从文献综述到实验设计&#xff0c;从数据分析到论文撰写&#xff0c;每个环节都需要投入大量时间和精力。特别是在deadline临近时&#xff0c;那种"时间不够…

作者头像 李华