这是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;
}
}
- 代码的开头部分
import "@stdlib/deploy";
:这个模块提供了与合约部署相关的功能和工具。import "@stdlib/ownable";
:引入所有权管理模块,使合约可以有明确的所有者,并控制某些关键操作。import "@stdlib/stoppable";
:允许合约能够被暂停和恢复运行,增加了对合约状态的控制。- 接着还有一些自定义模块的引入,如
messages
、saving_ton_wallet
和saving_jetton_wallet
(这次不会讨论,因为Jetton存款的逻辑和TON存款完全不一样),这些模块包含特定的消息定义和与钱包相关的功能实现。
- 合约定义
contract Ton401kMain with Deployable, OwnableTransferable, Resumable {... }
:定义了一个名为Ton401kMain
的合约,它继承了Deployable
、OwnableTransferable
和Resumable
三个特性。这意味着合约具有可部署性、所有权可转移以及可暂停和恢复的功能。
- 变量与常量定义
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;
:以总储蓄计划数量。
- 初始化函数
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!