mcpField
F
func2tolk

func2tolk

github.com/ton-blockchain开发13163 次安装17 周前更新
代码仓库

关于 func2tolk

func2tolk 是将旧版 FunC 智能合约迁移到 Tolk 的编码技能,面向 TON 合约开发者。它强调保留 TL-B 布局、操作码、错误码和可观察行为,并使用 Acton 工作流辅助转换、测试与验证。

主要功能

  1. 01保留 TL-B 布局和合约行为
  2. 02用类型化消息与联合类型处理分发
  3. 03将存储建模为结构体和类型化映射
  4. 04使用 Acton 构建和测试
  5. 05提供迁移清单与参考示例

适用场景

  1. 01将 FunC 合约迁移为 Tolk
  2. 02核对消息和存储布局兼容性
  3. 03把现有测试迁移为原生 Tolk 测试
  4. 04构建、测试并验证合约

安装

复制下面的命令,在终端中运行。

$npx skills add https://github.com/ton-blockchain/skills --skill func2tolk

安装后在 Claude Code 中输入/func2tolk确认已加载。

包含的文件

除 SKILL.md 外,安装时一起下载的参考资料和脚本。

agents/
openai.yaml
references/
porting-checklist.md
repo-examples.md

详细介绍

func2tolk

概述

将旧版 FunC 智能合约移植到 Tolk,同时保留:

  • TL-B 布局(存储和消息体)
  • 操作码编号和错误代码
  • 可观察行为(包括退回处理和发送模式)

开发框架优先使用 Acton(构建、包装器、原生 Tolk 测试、脚本、部署、验证)。

如果 acton func2tolk 能加快工作进度,可将其用作第一轮转换工具;随后应将输出视为草稿,进行审核并重构为符合 Tolk 惯用风格、可测试的代码。

常见映射和注意事项,请打开 references/porting-checklist.md。公开的惯用示例和原始 FunC 基线,请打开 references/repo-examples.md。

不可妥协的规则(必须遵守)

每次 FunC -> Tolk 移植都必须遵守以下规则。

  • 必须遵循本技能以及以下文档中的惯用写法:
    • https://docs.ton.org/languages/tolk/idioms-conventions
  • 对已知操作码族,必须使用类型化消息架构和联合分派(type Allowed... + lazy ...fromSlice(...) + match)。
  • 必须使用结构体进行存储和消息解码,而不是在业务逻辑中逐字段命令式解析。
  • 核心逻辑中必须使用类型化映射(map<K, V>)和类型化辅助函数;仅允许在边界处进行原始字典解析,并且必须立即封装。
  • 偏离惯用代码时,必须明确说明 TL-B 兼容性约束(例如:UnsafeBodyNoRef、any_address?、旧版内联消息体的特殊行为)。
  • 移植期间必须打开并应用 references/porting-checklist.md。
  • 在完成本文件中的强制完成检查之前,禁止结束移植。

除非为兼容性提供明确理由,否则禁止:

  • 对可建模为带标签结构体 + 联合 + match 的消息,使用手动操作码 if/elseif 分支链
  • 在可使用类型化结构体的领域逻辑中,临时进行消息解析(load_uint/load_msg_addr/...)
  • 在核心业务分支中散布原始 udict_* 操作,而不使用类型化映射辅助函数
  • 未按照检查清单进行自审就结束移植

可移植参考资料与示例集

  • 本技能目录中的参考资料:
    • references/porting-checklist.md
    • references/repo-examples.md
  • 主要公开风格参考:
    • https://github.com/ton-blockchain/acton-contracts
    • 每个合约套件都包含 Acton/Tolk 合约、原生测试、脚本、生成的包装器、相关基准快照,以及指向原始 FunC 实现的 README。
  • 值得重点研究的 Acton/Tolk 套件:
    • jetton-v2,配套 https://github.com/ton-blockchain/jetton-contract/tree/jetton-2.0
    • nft,配套 https://github.com/ton-blockchain/nft-contract
    • w5,配套 https://github.com/ton-blockchain/wallet-contract-v5
    • dns,配套 https://github.com/ton-blockchain/dns-contract
    • multisig-v2,配套 https://github.com/ton-blockchain/multisig-contract-v2
    • highload-v3,配套 https://github.com/ton-blockchain/highload-wallet-contract-v3
    • notcoin,配套 https://github.com/OpenBuilders/notcoin-contract
    • config,配套 https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/config-with-ownable-params.fc
    • elector,配套 https://github.com/ton-blockchain/ton/blob/master/crypto/smartcont/elector-code.fc
  • acton-contracts 中的 counter 没有对应的原始 FunC 实现;仅将其用于了解 Acton 项目的基本机制。
  • Acton 文档:
    • https://github.com/ton-blockchain/acton/tree/master/docs/content/docs
    • 否则请使用 acton --help 和官方托管文档
  • Tolk 惯用写法与约定(以文档为先的风格目标):
    • https://docs.ton.org/languages/tolk/idioms-conventions
  • 运行 acton ... 之前,先 cd 到包含 Acton.toml 的目录(Acton 会相对于该目录解析合约和配置)。

移植工作流(Acton 优先)

1) 确定行为契约
  • 定位 FunC 入口点(recv_internal、recv_external、run_ticktock、getters),并列出已处理的操作码。
  • 提取必须保留的不变量:
    • 精确的存储布局(位宽、字段顺序、引用的有无)
    • 消息 TL-B 布局(包括 Either 负载和 Maybe 字段)
    • Gas 和预留行为,以及发送模式
  • 将现有 JS/TS 测试视为规范;移植时将其转换为原生 Tolk 测试。
2) 设置 Acton 项目结构
  • 创建脚手架或复用现有项目:
    • acton new PROJECT --template empty|counter|jetton
  • 在 Acton.toml 的 [contracts.*] 下定义合约。
  • 使用 [mappings] 避免深层相对导入(采用 @stdlib/... 导入风格)。
  • 如果系统包含多个合约(Jetton 铸币合约 + 钱包、集合 + 项目等),请在 Acton.toml 中建模依赖,并使用 acton build --graph 检查依赖 DAG。
3) 优先转换存储和 TL-B 类型
  • 将类型拆分到可由测试/封装导入的文件中(避免将消息/存储结构体放在合约入口文件内):
    • errors.tolk — 错误码
    • messages.tolk — 消息 TL-B 结构体和联合类型
    • storage.tolk — 持久化结构体和辅助函数
  • 将合约数据建模为 struct Storage { ... }(及相关结构体)。
  • 实现存储辅助函数:
    • fun Storage.load() { return Storage.fromCell(contract.getData()) }
    • fun Storage.save(self) { contract.setData(self.toCell()) }
  • 如果 FunC 使用“可能尚未初始化”的存储,请明确建模(例如,使用一个从 slice 开始解析、检查剩余引用,再解析 Initialized 或 NotInitialized 变体的封装类型)。
4) 使用带标签的结构体转换消息体
  • 使用带标签的结构体替代 FunC 的 op::... 和手动解析:
    • struct (0x...) SomeMessage { ... }
  • 创建用于分发的联合类型:
    • type AllowedMessage = A | B | C
  • 使用惰性解码进行解析:
    • val msg = lazy AllowedMessage.fromSlice(in.body);
  • 为匹配旧版编码,优先使用正确的类型,而不是临时位操作:
    • RemainingBitsAndRefs 用于“剩余”负载/蛇形格式
    • Cell<T> 用于引用负载
    • 当旧版编码与 address? 不兼容时,使用 any_address?
5) 按 Acton 风格实现入口点
  • 内部消息:
    • fun onInternalMessage(in: InMessage) { ... }
  • 弹回消息:
    • fun onBouncedMessage(in: InMessageBounced) { ... }
    • 用它在发送失败时恢复余额/供应量(Jetton 中很常见)。
  • 外部消息(钱包、签名):
    • fun onExternalMessage(inMsgBody: slice) { ... }
  • 优先使用 match 进行分发;明确决定是忽略未知/空消息体,还是抛出 0xFFFF。
  • 如果必须在 onInternalMessage 内手动处理弹回,请使用编译器策略(例如 @on_bounced_policy("manual")),并说明原因。
6) 替换低级消息构建方式
  • 优先使用 createMessage({ ... }) + .send(SEND_MODE_...),而非手动进行 builder 编码。
  • 保持发送模式和弹回标志与 FunC 行为一致。
  • 移植依赖 raw_reserve、accept_message、存储费预留等功能的合约时,优先使用 @stdlib/gas-payments 辅助函数。
7) 迁移测试(JS/TS → 原生 Tolk)
  • 在 tests/*.test.tolk 中编写 Acton 测试。
  • 使用 net.treasury("name") 创建已注资的模拟账户。
  • 对返回的交易列表使用 expect(res) 匹配器。
  • 生成封装(推荐):
    • acton wrapper CONTRACT_ID --test
  • 将共享类型放在单独的文件中(例如 contracts/types.tolk),以便封装/测试可以导入它们,而无需导入合约入口文件(避免重复定义 onInternalMessage)。
8) 使用 Acton 工具迭代
  • 构建:acton build(调试时添加 --clear-cache)。
  • 测试:acton test(按需使用 --filter、--coverage、--debug)。
  • 调试编译差异:acton disasm(可用时使用 --source-map)。
  • 在本地试运行部署逻辑:acton script scripts/deploy.tolk(不会实际消耗 TON)。
  • 仅在本地运行成功后广播:
    • acton script scripts/deploy.tolk --broadcast --net testnet|mainnet
9) 在链上验证
  • 部署后:
    • acton verify CONTRACT_ID --address EQ... --net testnet|mainnet

高价值 FunC → 符合 Tolk 惯用写法的模式(来自 acton-contracts)

除非兼容性限制要求使用更底层的代码,否则请将本节作为默认的转换策略。

A) Opcode 分发:用带类型的联合类型 + match 替换 if (op == ...) 链

FunC 基线:

  • op = in_msg_body~load_uint(32); if (op == op::x) { ... } if (op == op::y) { ... }

符合 Tolk 惯用写法:

  • 在 messages.tolk 中声明带标签的消息结构体
  • 将它们组合成联合类型(type AllowedMessage = A | B | C)
  • 延迟解析并使用 match 分发
type AllowedMessage = Transfer | Burn | TopUp

fun onInternalMessage(in: InMessage) {
    val msg = lazy AllowedMessage.fromSlice(in.body);
    match (msg) {
        Transfer => { ... }
        Burn => { ... }
        TopUp => { ... }
        else => throw 0xFFFF
    }
}

这样做的好处:

  • 保持 opcode 兼容性,同时避免手动解析偏差
  • 让各分支的本地解析具有强类型
  • 更适用于大型 opcode 集合(wallet-v5/vesting/telemint 风格)
B) 将消息体解析为结构体,而不是临时加载

优先使用:

  • 对外可见的 opcode 使用 struct (0x...)
  • 对分组负载使用嵌套结构体(TransferOwnershipData、MsgInner 等)
  • 负载明确按引用传递时使用 Cell<T>
  • 对真正的剩余负载 / snake / either 包装器使用 RemainingBitsAndRefs

避免:

  • 在业务逻辑中反复使用 load_uint/load_msg_addr/load_ref,而结构体本可以表示相同的布局。
C) 使用专用的 messages.tolk 模块定义入站格式

移植中广泛使用的模式:

  • 将所有传入消息的模式保存在 messages.tolk 中
  • 将消息类型导入合约入口
  • 在消息定义附近定义 Allowed... 联合类型

这样可让入口文件专注于行为,而非字节解析。

D) 存储转换:将元组加载器替换为类型化存储结构体 + 方法

FunC 基线:

  • (a, b, c) load_data() 和 save_data(a, b, c)

符合 Tolk 惯用写法:

  • struct Storage { ... }
  • Storage.load() / Storage.save()
  • 保持 TL-B 布局一致(字段顺序 + 引用/内联形态)
struct Storage { totalSupply: coins adminAddress: address? }
fun Storage.load() { return Storage.fromCell(contract.getData()) }
fun Storage.save(self) { contract.setData(self.toCell()) }
E) 显式建模多形态状态(已初始化 vs 未初始化)

NFT/DNS/Telemint 项目中常见:

  • FunC 会检查 slice_bits() > 0 或引用是否存在,以判断形态。

符合 Tolk 惯用写法:

  • 加载器包装器 + 显式解析器:
    • startLoading...()
    • .isInitialized()
    • .parseNotInitialized()
    • .parseInitialized()

这样可明确状态转换,并避免脆弱的剩余内容解析。

F) 字典:将原始 udict_* 操作替换为类型化映射

FunC 基线:

  • 手动使用 udict_get?、udict_set_ref、udict_delete?、udict_delete_get_min

符合 Tolk 惯用写法:

  • map<K, V> 别名 + 类型化辅助方法(exists、set、delete、findFirst、iterateNext)
  • 仅在边界处转换底层字典(createMapFromLowLevelDict(...))

示例:

  • DNS 记录:type DnsRecords = map<uint256, cell>
  • 钱包扩展:map<uint256, bool>
  • 查询位图:map<uint13, Bitmap>
G) 优先使用 createMessage(...),而不是手动组装 begin_cell() 消息

默认做法:

  • 使用 createMessage({ ... }) 构建传出消息
  • 通过 .send(SEND_MODE_...) 发送

仅在以下情况下保留手动构建器:

  • 精确模拟历史行为差异
  • 强制使用精确的内联/无引用消息体布局
H) 外部签名流程:有意解析后缀/剩余内容

对于钱包风格的外部消息:

  • 将签名与签名负载分离(使用 getLastBits/removeLastBits 或显式字段顺序)
  • 先验证哈希/签名
  • 更新重放状态(seqno / 查询映射),持久化并提交
  • 然后执行操作/发送消息

这与经过强化的钱包 v5/高负载模式一致。

I) Bounce 处理:优先使用 onBouncedMessage 和带类型的 bounce 联合类型

FunC 基线:

  • recv_internal 中的 bounce 分支 + 手动 load_uint

符合 Tolk 惯用风格的写法:

  • onBouncedMessage(in: InMessageBounced)
  • 可选的 bounce 操作码子集联合类型:
    • type BounceOpToHandle = A | B
  • 从带类型的解析消息中恢复余额/供应量
J) 保留旧版 TL-B/行为的兼容性选项

与 FunC 测试保持一致时,有意使用以下选项:

  • 使用 UnsafeBodyNoRef { forceInline: ... } 保持无引用体的兼容性
  • 当旧版布局比 address? 更宽泛时,使用 any_address?
  • 使用显式的 (Either Cell ^Cell) 剩余部分验证辅助函数确保载荷正确性
  • 保持未知操作码的行为一致(throw 0xFFFF 与忽略空消息体)
K) 从实际移植中提炼的领域专用方案

Jetton 系列:

  • AllowedMessageToMinter / AllowedMessageToWallet
  • 带类型的 bounce 恢复(InternalTransferStep | BurnNotification...)
  • 带类型的转发载荷(RemainingBitsAndRefs)+ 验证器辅助函数

NFT + DNS 项:

  • 明确的未初始化->已初始化转换路径
  • 将所有权转移载荷作为嵌套结构体
  • 使用基于映射的内容记录,而不是命令式地修改字典位

Highload 钱包:

  • 尽早验证最小结构,然后进行带类型解析
  • 将重放位图建模为带类型的映射 + 辅助方法
  • 转发前对出站原始消息结构进行带类型验证

Vesting / 策略钱包:

  • 将白名单映射表示为带类型的 map<address, ()>
  • 使用联合类型解析 + match 验证允许的操作码(而不是在各分支中分散数值操作码白名单)

强制完成门槛(每次移植任务结束前)

完成以下所有检查项后,才能结束任务。

  1. 已打开并应用检查清单:
    • references/porting-checklist.md
  2. 分发逻辑符合惯用风格:
    • 已知消息系列使用带标签的结构体 + 联合类型 + match
  3. 解析采用类型化方式:
    • 业务分支中没有可避免的命令式解析
  4. 存储采用类型化方式:
    • 使用 Storage.load() / Storage.save()(或显式的已初始化/未初始化加载器模式)
  5. Dict 使用采用类型化方式:
    • 逻辑中使用 map<K,V> + 辅助函数;边界处的原始 dict 解码已封装
  6. 已记录兼容性例外:
    • 任何非惯用结构均已说明其作为 TL-B/行为一致性要求的理由
  7. 已报告构建/测试状态:
    • 运行相关的 acton build/acton test(或报告确切阻碍)
  8. 最终回复包含针对上述门槛的简短自审摘要。

如果任何门槛项目无法完成,不要悄然结束。请报告阻碍,并明确列出未完成的门槛项目。

将 acton-contracts 用作风格参考

对于生产级移植,默认使用 https://github.com/ton-blockchain/acton-contracts 作为公开风格参考:

  • 使用各套件的 README 查找原始 FunC 仓库或文件
  • 使用原始 FunC 源码作为行为基线,尤其要关注边缘情况
  • 使用对应的 Acton/Tolk 套件作为模块布局、类型化存储、消息联合类型、包装器、脚本、测试和基准快照的目标风格

优先按以下内容搜索:

  • 操作码十六进制值(例如 0xd53276db)
  • 消息类型名称(例如 InternalTransferStep)
  • 存储字段名称(例如 totalSupply、ownerAddress)

打开 references/repo-examples.md 查看套件映射、原始 FunC 链接和搜索目标。

文档优先查阅

  • 对于 Tolk 语言细节和标准库,请使用 TON Docs 中 languages/tolk/ 下的页面(概览、序列化、标准库、FunC 对比)。
  • 对于 Acton 的具体用法(命令、包装器、测试、脚本),请查阅 Acton 文档和 acton --help。

评论

支持 Markdown