首先对之前编写的合约的初始化,因为刚刚引入了一个新的变量,该变量应存储在c4存储中。
然后更新合约包装器,以便它能够识别c4存储中的第三个参数。更新
MainContractConfig
和mainContractConfigToCell
函数:// ...library imports export type MainContractConfig = { number: number; address: Address; owner_address: Address; }; export function mainContractConfigToCell(config: MainContractConfig): Cell { return beginCell() .storeUint(config.number, 32) .storeAddress(config.address) .storeAddress(config.owner_address) .endCell(); } // ...contact wrapper class
还需要在包装器文件中更新
getData
方法,以便它也返回owner_address
,并创建一个新方法getBalance
,以测试存款后的余额:async getData(provider: ContractProvider) { const { stack } = await provider.get("get_contract_storage_data", []); return { number: stack.readNumber(), recent_sender: stack.readAddress(), owner_address: stack.readAddress(), }; } async getBalance(provider: ContractProvider) { const { stack } = await provider.get("balance", []); return { number: stack.readNumber(), }; }
通过treasury引入一个新钱包地址当作是合约的所有者,只有这个所有者以后可以提取资金。在
tests/main.spec.ts
中完成的:// ...library imports 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 initAddress = await blockchain.treasury("initAddress"); const ownerAddress = await blockchain.treasury("ownerAddress"); const myContract = blockchain.openContract( await MainContract.createFromConfig( { number: 0, address: initAddress.address, owner_address: ownerAddress.address, // 现在我们用3个参数创建myContract }, codeCell ) ); // ...rest of testing code
现在一切正常。执行
yarn test
,先前编写的测试成功通过。现在继续在
main.spec.ts
文件中添加存款测试由于我们必须在每个新测试之前重新初始化一个合约,因此我们也将在Jest的
beforeEach
函数中引入合约初始化:// 我们需要另外导入SandboxContract和TreasuryContract import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox"; // ... other library imports describe("main.fc contract tests", () => { let blockchain: Blockchain; let myContract: SandboxContract<MainContract>; let initWallet: SandboxContract<TreasuryContract>; let ownerWallet: SandboxContract<TreasuryContract>; beforeEach(async () => { blockchain = await Blockchain.create(); initWallet = await blockchain.treasury("initWallet"); ownerWallet = await blockchain.treasury("ownerWallet"); const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0]; myContract = blockchain.openContract( await MainContract.createFromConfig( { number: 0, address: initWallet.address, owner_address: ownerWallet.address, }, codeCell ) ); }); it("should get the proper most recent sender address", async () => { const senderWallet = await blockchain.treasury("sender"); const sentMessageResult = await myContract.sendIncrement( senderWallet.getSender(), toNano("0.05"), 1 ); 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()); expect(data.number).toEqual(1); }); it("successfully deposits funds", () => { // test logic is coming }); it("should return deposit funds as no command is sent", () => { // test logic is coming }); it("successfully withdraws funds on behalf of owner", () => { // test logic is coming }); it("fails to withdraw funds on behalf of non-owner", () => { // test logic is coming }); it("fails to withdraw funds because lack of balance", () => { // test logic is coming }); });
只用将第一个测试的一部分代码移动到
beforeEach
中,其余代码保持在第一个测试中。为存款编写一个测试。发送一个
op == 2
的消息,然后检查余额。首先创建一个包装器方法,称之为sendDeposit
:async sendDeposit(provider: ContractProvider, sender: Sender, value: bigint) { const msg_body = beginCell() .storeUint(2, 32) // 操作码 .endCell(); await provider.internal(sender, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: msg_body, }); }
这个方法非常简单——我们只是将操作码传递到消息体中。
以下是我们如何使用此方法运行存款测试:
it("successfully deposits funds", async () => { const senderWallet = await blockchain.treasury("sender"); const depositMessageResult = await myContract.sendDeposit( senderWallet.getSender(), toNano("5") ); expect(depositMessageResult.transactions).toHaveTransaction({ from: senderWallet.address, to: myContract.address, success: true, }); const balanceRequest = await myContract.getBalance(); expect(balanceRequest.number).toBeGreaterThan(toNano("4.99")); });
注意如何检查合约余额大于4.99 TON,一些资金是会因手续费丢失的。
为存款编写另一个测试,但这次它将检验错误情形。我将发送没有存款操作码的资金,并期望返回交。
再次创建一个包装器:
async sendNoCodeDeposit(provider: ContractProvider, sender: Sender, value: bigint) { const msg_body = beginCell().endCell(); await provider.internal(sender, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: msg_body, }); }
以下是测试代码:
it("should return funds as no command is sent", async () => { const senderWallet = await blockchain.treasury("sender"); const depositMessageResult = await myContract.sendNoCodeDeposit( senderWallet.getSender(), toNano("5") ); expect(depositMessageResult.transactions).toHaveTransaction({ from: myContract.address, to: senderWallet.address, success: true, }); const balanceRequest = await myContract.getBalance(); expect(balanceRequest.number).toBe(0); });
资金提取测试
首先为资金提取创建一个新的包装器:
async sendWithdrawalRequest(provider: ContractProvider, sender: Sender, value: bigint, amount: bigint) { const msg_body = beginCell() .storeUint(3, 32) // 操作码 .storeCoins(amount) .endCell(); await provider.internal(sender, { value, sendMode: SendMode.PAY_GAS_SEPARATELY, body: msg_body, }); }
将适当的
msg_body
和希望提取的金额放入其中,将有三个不同的测试。it("successfully withdraws funds on behalf of owner", async () => { const senderWallet = await blockchain.treasury("sender"); await myContract.sendDeposit(senderWallet.getSender(), toNano("5")); const withdrawalRequestResult = await myContract.sendWithdrawalRequest( ownerWallet.getSender(), toNano("0.05"), toNano("1") ); expect(withdrawalRequestResult.transactions).toHaveTransaction({ from: myContract.address, to: ownerWallet.address, success: true, value: toNano(1), }); });
为了成功提取资金,首先适当地存入资金。然后调用
sendWithdrawalRequest
,指定要提取1 TON。注意0.05 TON仅用于支付消息费用。it("fails to withdraw funds on behalf of not-owner", async () => { const senderWallet = await blockchain.treasury("sender"); await myContract.sendDeposit(senderWallet.getSender(), toNano("5")); const withdrawalRequestResult = await myContract.sendWithdrawalRequest( senderWallet.getSender(), toNano("0.5"), toNano("1") ); expect(withdrawalRequestResult.transactions).toHaveTransaction({ from: senderWallet.address, to: myContract.address, success: false, exitCode: 103, }); });
在此测试中,我们代表
senderWallet
发送提取请求,而不是ownerWallet
(由于在初始化时提供的种子短语不同所以它们不同)。注意如何等待交易失败,
success: false
和特定失败原因exitCode: 103
。为了确保做得正确,请查看我们的合约代码,其中有这样一行:throw_unless(103, equal_slice_bits(sender_address, owner_address));
最后一个测试——因余额不足而提取失败:
it("fails to withdraw funds because lack of balance", async () => { const withdrawalRequestResult = await myContract.sendWithdrawalRequest( ownerWallet.getSender(), toNano("0.5"), toNano("1") ); expect(withdrawalRequestResult.transactions).toHaveTransaction({ from: ownerWallet.address, to: myContract.address, success: false, exitCode: 104, }); });
在此测试中删除了存款逻辑,因此合约在开始时有0资金,这一段我们期望它报错并显示
exitCode: 104
。再次参考合约中的这行代码:throw_unless(104, balance >= withdraw_amount);
这样就完成了对之前存取款合约的所有测试示例的撰写。