TON 链上社保 (TON401K)合约开发 2 – 主合约解释

这是TON401K 系列的第五篇文章,以下为其他相关内容


在上篇文章中,我們已經說明了TON401k 智能合约的功能要求,也介紹了分片概念並且已列出所有代碼,接下來我們會盡可能詳盡的解释代碼的內容,并展示如何用blueprint 自带的回测功具进行测试。

引入模块变量

import "@stdlib/deploy";
import "@stdlib/ownable";
import "@stdlib/stoppable";
import "./messages";
import "./saving_ton_wallet";
import "./saving_jetton_wallet";
contract Ton401kMain with Deployable, OwnableTransferable, Resumable {
    const GAS_CONSUMPTION: Int = ton("0.01");
    owner: Address;
    operator: Address;
    stopped: Bool;
    listOfJetton: map<Address, Int>;
    totalTonSaved: Int as coins;
    totalSavingPlan: Int as uint8;
    //...
    init() {
        self.owner = sender();
        self.operator = sender();
        self.stopped = false;
        self.listOfJetton = emptyMap();
        self.totalTonSaved = 0;
        self.totalSavingPlan =0;
    }
}
  1. 代码的开头部分
    • import "@stdlib/deploy";:这个模块提供了与合约部署相关的功能和工具。
    • import "@stdlib/ownable";:引入所有权管理模块,使合约可以有明确的所有者,并控制某些关键操作。
    • import "@stdlib/stoppable";:允许合约能够被暂停和恢复运行,增加了对合约状态的控制。
    • 接着还有一些自定义模块的引入,如messagessaving_ton_walletsaving_jetton_wallet (这次不会讨论,因为Jetton存款的逻辑和TON存款完全不一样),这些模块包含特定的消息定义和与钱包相关的功能实现。
  2. 合约定义
    • contract Ton401kMain with Deployable, OwnableTransferable, Resumable {... }:定义了一个名为Ton401kMain的合约,它继承了DeployableOwnableTransferableResumable三个特性。这意味着合约具有可部署性、所有权可转移以及可暂停和恢复的功能。
  3. 变量与常量定义
    • const GAS_CONSUMPTION: Int = ton("0.01");:定义了一个常量,表示合约执行某些操作时的气体消耗。这里假设最小单位为 0.01 TON。
    • 以下是一些重要的变量:
      • owner: Address;:所有者地址。
      • operator: Address;:所有者可以指定一个操作员地址,用于协助管理合约。
      • stopped: Bool;:用于表示合约是否已停止运行,是由于继承了Resumable特性而必须添加的状态变量。
      • listOfJetton: map<Address, Int>;:这是一个映射,用于存储支持的 Jettons 的地址以及相关状态。//不会讨论
      • totalTonSaved: Int as coins;:以整数类型存储的 TON 总储蓄量。
      • totalSavingPlan: Int as uint32;:以总储蓄计划数量。
  4. 初始化函数
    • init()函数在合约创建时自动调用,用于设置合约的初始状态

接收函数与消息处理

receive(msg: StartTonSavingPlan)

当用户要开设自己的储蓄计划时,只需要向合约发送 StartTonSavingPlan 的指令。这时,我们的主合约会替用户部署一个退休计划合约,这种做法是Tom上的独特写法, TON的智能合约不允许无限延伸的数据结构,如果遇到这种没有边界的变量,我们便要对智能合约进行分片处理(詳情可參考 TON 链上社保 (TON401K)合约开发 1 – 基础结构 )。在TON401k 中,这样做亦有另外一个好处,就是把用户的资金分散处理,每一个用户都有自己独立的储蓄计划钱包,而且亦只能回到用户的帐户,大幅降低各种信用违约风险。

receive(msg: StartTonSavingPlan){
    self.requireNotStopped();
    let planOwner: Address = sender();
    let purpose: String  = msg.purpose;
    let targetAmount: Int = msg.target;
    let deadline: Int = msg.deadline;
    let lockup: Bool = msg.lockup;
    let today: Int = now();
    require(targetAmount > 0, "your plan must have a saving target target!");
    require(deadline > today, "your can only plan for your future!");

    let init: StateInit = initOf SavingTonWallet(myAddress(), planOwner);
    self.totalSavingPlan = self.totalSavingPlan+1;

    send(SendParameters{
            to: contractAddress(init),
            body: M2CSavingPlan{purpose: purpose, target: targetAmount, deadline: deadline, lockup: lockup}.toCell(),
            value: self.GAS_CONSUMPTION+self.GAS_CONSUMPTION+self.GAS_CONSUMPTION+self.GAS_CONSUMPTION, 
            mode: SendIgnoreErrors,
            code: init.code,
            data: init.data
        }
    );
}
  • 首先,检查合约是否已停止运行。如果合约已停止,则不能启动新的储蓄计划。
  • 从消息中获取计划所有者、目的、目标金额、截止日期和锁定状态等信息。
  • 进行一些有效性检查,确保目标金额大于 0,截止日期在当前时间之后。
  • 如果检查通过,创建一个新的SavingTonWallet的状态初始化对象,并增加总储蓄计划数量。這裡會為用戶部署一個儲蓄錢包,這
  • 最后,发送一个包含储蓄计划参数的消息到新创建的钱包地址,同时设置发送参数。
    • lockup 参数设定为true 的话,意味着用户在到期或达成储蓄目标之前都不能取出存款。

receive(msg: StartTonSavingPlan)

当用户要存款到自己的储蓄计划时,只需要向合约发送 saving 文本的指令。

receive("saving"){
    let ctx: Context = context();
    let savingAmt = ctx.value;
    let planOwner: Address = sender();
    let init: StateInit = initOf SavingTonWallet(myAddress(), planOwner);
    self.totalTonSaved = self.totalTonSaved + savingAmt;

    send(SendParameters{
            to: contractAddress(init),
            body: "saving".asComment(),
            value: savingAmt - self.GAS_CONSUMPTION, 
            mode: SendIgnoreErrors,
            code: init.code,
            data: init.data
        }
    );
}
  • 首先,获取上context信息,包括发送者sender 和储蓄金额。
  • 然后,创建一个新的SavingTonWallet的状态初始化对象,用于后续的操作。
  • 更新totalTonSaved变量,增加储蓄金额。
  • 最后,发送一个包含储蓄金额减去gas消耗的消息到钱包地址,同时设置发送模式。

receive(msg: StartTonSavingPlan)

这个比较特别,之前提到lockup 参数设定为true 的话,意味着用户在到期或达成储蓄目标之前都不能取出存款。但如果用户遇到必须要取回存款的时候,可以联络管理员,由管理员发送ForceWithdrawTON指令到用户的储蓄钱包,取回存款。

receive(msg: ForceWithdrawTON){
    require(sender() == self.owner || sender() == self.operator, "Only owner and operator can do this");
    let init: StateInit = initOf SavingTonWallet(myAddress(), msg.walletOwner);

    send(SendParameters{
            to: contractAddress(init),
            body: "forceWithdraw".asComment(),
            value: self.GAS_CONSUMPTION+self.GAS_CONSUMPTION, 
            mode: SendIgnoreErrors,
            code: init.code,
            data: init.data
        }
    );
}
  • 要求发送者是合约的所有者或操作员。
  • 发送一个包含 “forceWithdraw” 的消息到指定钱包地址,同时设置发送参数。

Getter 获取函数

这个获取函数用于获取特定所有者的 TON 储蓄钱包地址。

get fun SavingTonWalletAddress(owner: Address): Address {
    let expectedAddress: Address = contractAddress(initOf SavingTonWallet(myAddress(), owner));
    return expectedAddress;
}

受篇幅所限,我们吧TON 储蓄钱包 saving_ton_wallet.tact 和测试的部分留待下次的更新。


我是TonPanda,TON和ICP的早期投资者,资深以太坊开发者,也是一名热爱去中心化的小韭菜,现时主要开发Telegram和TON生态应用。欢迎关注我们的 X:https://x.com/ton_org 和 飞机群 @tonchina

Leave a Comment