这是TON401K 系列的第六篇文章,以下为其他相关内容
TON401k 一共由 ton401k_main.tact(TON401K 主合约)saving_ton_wallet.tact(TON 储蓄计划子合约) 和 messages.tact(自定义消息管理)三部分組成,在上篇文章中,我們分析了ton401k_main.tact 代碼的內容,這一次我們會分析子合約,并展示如何用blueprint 自带的回测功具进行测试。
引入模块与变量
和主合约差不多,首先引入模块与变量
import "@stdlib/deploy";
import "./messages";
struct SavingPlan {
...
}
struct SavingStat {
...
}
contract SavingTonWallet with Deployable {
//TON takes storage fee, you need to keep some TON in the contract for the storage
const MIN_TON_FOR_STORAGE: Int = ton("0.01");// 1KB for 2.5 yr
const GAS_CONSUMPTION: Int = ton("0.01"); // assuming the minium unit of gasConsumption is 0.01 TON
owner: Address; // only the wallet own can control the fund in the wallet
parent: Address; // saving plan can only be initiated by the master contract
// fields for saving plan
lockup: Bool;
ongoing: Bool; // ongoing plan
targetReached: Bool; // ongoing plan
target: Int as coins; // number of TON user wants to save
purpose: String;
startingTime: Int as uint32;
deadline: Int as uint32;
lastSavingDate: Int as uint32;
numberOfDeposit: Int as uint16;
minDeposit: Int as coins; // the min amount of each deposit
// fields for overall record
totalSaving: Int as coins;
planFinished: Int as uint16;
planFailed: Int as uint16;
totalNumberOfDeposit: Int as uint32;
planTime: Int as uint32; // the total number of second for all plan
OFS: Int as uint32; //the on-chain financial score
...
}
定义struct
struct SavingPlan
:用于表示储蓄计划的结构体,包含了锁定期状态、计划是否正在进行、目标储蓄金额、储蓄目的、开始时间、截止时间、最后储蓄日期、存款次数等字段。struct SavingStat
:用于表示储蓄统计信息的结构体,包含了总储蓄金额、完成的计划数量、失败的计划数量、总存款次数、所有计划的总时间、链上财务评分(OFS)等字段。
变量定义
owner: Address
:存储钱包的所有者地址,只有所有者可以控制钱包中的资金。parent: Address
:表示发起储蓄计划的主合约地址,ton401k_main.tact。- 储蓄计划相关变量:
lockup: Bool
:表示是否有锁定期。ongoing: Bool
:表示储蓄计划是否正在进行。targetReached: Bool
:表示是否达到了目标储蓄金额。target: Int as coins
:用户想要储蓄的 TON 数量目标。purpose: String
:储蓄目的。startingTime: Int as uint32
:储蓄计划的开始时间。deadline: Int as uint32
:储蓄计划的截止时间。lastSavingDate: Int as uint32
:最后一次储蓄的日期。numberOfDeposit: Int as uint16
:存款次数。minDeposit: Int as coins
:每次存款的最小金额。
- 储蓄统计相关变量:
totalSaving: Int as coins
:总储蓄金额。planFinished: Int as uint16
:完成的计划数量。planFailed: Int as uint16
:失败的计划数量。totalNumberOfDeposit: Int as uint32
:总存款次数。planTime: Int as uint32
:所有计划的总时间。OFS: Int as uint32
:链上财务评分。
接收函数与消息处理
receive(msg: M2CSavingPlan)
receive(msg: M2CSavingPlan){
require(sender() == self.parent, "please use the default function");
require(!self.ongoing, "no on going plan");
require(msg.deadline > now(), "your can only plan for your future!");
self.purpose = msg.purpose;
self.target = msg.target;
self.deadline = msg.deadline;
self.lockup = msg.lockup;
self.ongoing = true;
self.targetReached = false;
self.startingTime = now();
if((self.deadline - self.startingTime)< (60*60*24)){
self.minDeposit = self.target;
}else{
self.minDeposit = self.target / ((self.deadline - self.startingTime)/(60*60*24))
}
}
- M2C 是自定的消息,意为Master to Child
- 首先,检查发送者是否为主合约地址,确保只有主合约可以创建储蓄计划。
- 检查是否已经有正在进行的计划,如果有则不能创建新的计划。
- 检查截止时间是否在未来。
- 如果满足条件,设置储蓄计划的目的、目标金额、截止时间、锁定期等信息。
- 根据截止时间和目标金额计算最小存款金额。
receive(“saving”)
这个接收函数用于处理存款操作。
receive("saving"){
let ctx: Context = context();
let savingAmt = ctx.value;
require(sender() == self.parent, "owner wallet can only deposit to the account via the main contract");
require(savingAmt>= self.minDeposit, "you need to deposit more than the minimum desposit");
let contractBalance = myBalance() + savingAmt;
if((now() - self.lastSavingDate) >= 24*60*60 ){
self.numberOfDeposit = self.numberOfDeposit + 1;
self.OFS = self.OFS + self.numberOfDeposit * savingAmt/ ton("0.1");
}
self.lastSavingDate = now();
if(contractBalance >= self.target){
self.targetReached = true;
}
}
- 检查发送者是否为主合约地址,确保只有主合约可以进行存款操作。
- 其实可以直接允许用户存款,然后再由子合约发送消息到主合约以作记录,但我们这次选择的是用户存款到主合约,再由主合约把存款发送到子合约。
- 检查存款金额是否大于等于最小存款金额。
- 如果当前时间与最后一次存款日期相差超过一天,则增加存款次数,并更新 OFS(链上财务评分)。
- 更新最后一次存款日期。
- 如果当前合约余额大于等于目标金额,则设置目标达成状态为真。
receive(“withdraw”)
这个接收函数用于处理用户的取款操作。
receive("withdraw"){
let ctx: Context = context();
require(sender() == self.owner, "owner wallet owner can withdraw from the account");
require(self.ongoing, "you don't have a saving plan");
if(self.lockup){
require((self.targetReached || self.deadline < now()), "you haven't reached the goal yet! You need to reach the goal or wait until the deadline");
}
let amount: Int = myBalance();
self.checkPlanWhenWithdraw();
send(SendParameters{
to: self.owner,
body: "withdrawal from saving account".asComment(),
value: amount,
mode: SendIgnoreErrors | SendRemainingBalance
}
);
}
- 检查发送者是否为钱包所有者。
- 检查是否有正在进行的储蓄计划。
- 如果有锁定期,检查是否已经达到目标金额或者截止时间已过。
- 获取当前合约余额。
- 调用
checkPlanWhenWithdraw
函数进行取款后的计划检查和统计更新。 - 发送包含取款金额的消息给钱包所有者。
receive(“forceWithdraw”)
这个接收函数用于处理紧急取款操作,只能由主合约的所有者或操作员触发(owner or operator,可参阅上一篇文章)。
receive("forceWithdraw"){
let ctx: Context = context();
require(sender() == self.parent, "this is an emergency withdraw that can only be triggered by contract owner or operater");
let amount: Int = myBalance();
self.checkPlanWhenWithdraw();
send(SendParameters{
to: self.owner,
body: "forced withdrawal from saving account".asComment(),
value: amount,
mode: SendIgnoreErrors | SendRemainingBalance
}
);
}
- 检查发送者是否为主合约地址。
- 获取当前合约余额。
- 调用
checkPlanWhenWithdraw
函数进行取款后的计划检查和统计更新。 - 发送包含取款金额的消息给钱包所有者。
辅助函数和Getter
fun checkPlanWhenWithdraw()
fun checkPlanWhenWithdraw(){
self.ongoing = false;
let planDuration: Int = now() - self.startingTime;
let roundScore: Int = 0;
if(self.deadline > self.lastSavingDate && self.targetReached){
self.planFinished = self.planFinished + 1;
roundScore = roundScore + planDuration / (60*60*24);
}
else{
self.planFailed = self.planFailed + 1;
}
self.planTime = self.planTime+ planDuration;
self.totalNumberOfDeposit = self.totalNumberOfDeposit + self.numberOfDeposit;
self.totalSaving = self.totalSaving + myBalance();
if(self.numberOfDeposit>6){
roundScore = roundScore + myBalance() / ton("1") * self.numberOfDeposit;
}
if(self.lockup && self.deadline > self.lastSavingDate && self.targetReached){
roundScore = roundScore*2;
}
self.OFS = self.OFS + roundScore;
}
- 将正在ongoing设置为 false。
- 计算储蓄计划的持续时间。
- 根据不同情况计算得分。
- 如果截止时间大于最后一次存款日期且目标已达成,则增加完成计划数量,并根据计划持续时间计算得分。
- 如果不满足上述条件,则增加失败计划数量。
- 更新总存款次数、总储蓄金额和计划总时间。
- 如果存款次数大于 6,则根据存款次数和余额计算得分。
- 如果有锁定期且目标已达成和截止时间未过,则得分加倍。
- 更新链上财务评分。
get fun balance(): Int
这个获取函数用于获取当前合约的余额。
get fun savingPlan(): SavingPlan
这个获取函数用于返回当前的储蓄计划状态。
get fun savingStat(): SavingStat
这个获取函数用于返回当前的储蓄统计信息。
测试TON 智能合约
TON Blueprint 的TypeScript SDK 类似以太坊的truffle,都有内建的沙盒测试功能,可以不用每次都部署到测试网进行测试,加快开发流程。一般的开发测试程都是这样,先在本地沙盒运行不同的test cases,都成功后再部署到Testnet 进行链上交互。
要執行測試也很簡單,在 tests/Ton401kMain.spec.ts 中編寫測試代碼,然後運行:
npx blueprint test
之后马上可以知道合约有没有异常
我是TonPanda,TON和ICP的早期投资者,资深以太坊开发者,也是一名热爱去中心化的小韭菜,现时主要开发Telegram和TON生态应用。欢迎关注我们的 X:https://x.com/ton_org 和 飞机群 @tonchina!