【TON】【官网基础系列二】TON 合约测试
下载依赖
yarn add @ton/sandbox jest ts-jest @types/jest @ton/ton @ton/test-util --dev
TON 开发了 sanbox 库仿真 TON 智能合约,本章节针对 sandbox
和 jest
展开 TON 合约测试。
代码实现
main.fc
contracts/main.fc
FunC 实现合约代码为基础教程 3.3 章节的内容,stdlib.fc 下载地址如下,放在 contracts/imports
目录下。
#include "imports/stdlib.fc";
() recv_internal(int msg_value, cell in_msg, slice in_msg_body) impure {
slice cs = in_msg.begin_parse();
int flags = cs~load_uint(4);
slice sender_address = cs~load_msg_addr();
set_data(begin_cell().store_slice(sender_address).end_cell());
}
slice get_the_latest_sender() method_id {
slice ds = get_data().begin_parse();
return ds~load_msg_addr();
}
接下来我们来看看这段代码干了啥:
其中
msg_value
和in_msg_body
都可以从in_msg
派生,但为了便于使用,我们将它们作为参数接收到receive_internal()
函数中。
msg_value
:参数表明该报文收到了多少TON币(或gram)。in_msg
:收到的一条完整信息,包含发送者等所有信息。我们可以看到它的类型是 Cell。这意味着什么呢?信息正文作为一个cell
存储在 TVM 上,因此有一个完整的Cell专门用于存储我们的信息及其所有数据。in_msg_body
:表示收到的信息中实际可读的部分。是一个slice
类型,是 Cell 的一部分,指出了如果我们要读取这个片段参数,应该从Cell的哪个部分开始读取的 “地址”。
什么是 Cell (TODO)
传入函数后的修饰参数可选的有
impure
、inline/inline_ref
、method_id
三个:
impure
用于表示修改合约状态,处理外部消息和执行状态更新操作;函数未指定impure
且函数调用的结果未被使用,FunC 编译器可能会删除该函数调用。inline/inline_ref
用于定义内联函数,常用于提高性能并避免函数调用开销。method_id
用于定义公共函数,允许通过外部消息调用合约中的特定函数。
jest.config.js
jest 配置文件:
module.exports = {
preset: 'ts-jest',
testEnvironment: 'node',
};
什么是 jest(TODO)
package.json
添加jest 相关指令:
{
...
"scripts": {
"compile": "ts-node ./scripts/compile.ts",
"test": "yarn compile && yarn jest"
},
...
MainContract.ts
wrapper/MainContract.ts
该文件将实现并导出我们合约的包装器,确保从项目根目录运行此命令序列。
import { Address, beginCell, Cell, Contract, contractAddress, ContractProvider, Sender, SendMode } from "@ton/core";
export class MainContract implements Contract {
constructor(
readonly address: Address,
readonly init?: { code: Cell; data: Cell }
) {}
static createFromConfig(config: any, code: Cell, workchain = 0) {
const data = beginCell().endCell();
const init = { code, data };
const address = contractAddress(workchain, init);
return new MainContract(address, init);
}
async sendInternalMessage(
provider: ContractProvider,
sender: Sender,
value: bigint
) {
await provider.internal(sender, {
value,
sendMode: SendMode.PAY_GAS_SEPARATELY,
body: beginCell().endCell(),
});
}
async getData(provider: ContractProvider) {
const { stack } = await provider.get("get_the_latest_sender", []);
return {
recent_sender: stack.readAddress(),
};
}
}
为什么需要这个 wrapper(TODO)
main.spec.ts
tests/main.spec.ts
代码如下:
import { Cell, toNano } from "@ton/core";
import { hex } from "../build/main.compiled.json";
import { Blockchain } from "@ton/sandbox";
import { MainContract } from "../wrappers/MainContract";
import "@ton/test-utils";
describe("main.fc contract tests", () => {
it("should get the proper most recent sender address", async () => {
const blockchain = await Blockchain.create();
const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
const myContract = blockchain.openContract(
await MainContract.createFromConfig({}, codeCell)
);
const senderWallet = await blockchain.treasury("sender");
const sentMessageResult = await myContract.sendInternalMessage(
senderWallet.getSender(),
toNano("0.05")
);
expect(sentMessageResult.transactions).toHaveTransaction({
from: senderWallet.address,
to: myContract.address,
success: true,
});
const data = await myContract.getData();
expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());
});
});
这个文件又是干嘛的