在本课中,我们将编写一些有趣的测试。 首先,我们要更新之前编写的合约的初始化过程,因为我们引入了一个新变量,它应该存储在 c4 存储器中。 9 - Cell下溢。从片断基元读取数据时,试图读取的位或引用数超过总数。 更新我们的合约包装器首先,让我们更新我们的合约包装器,以便它能意识到 c4 存储中的第三个参数。也就是说,我们将更新 MainContractConfig 和 mainContractConfigToCell 函数 - // ...library imports
* T; h: ]7 M) ^( k - ! f' S, d6 L' j: y/ d
- export type MainContractConfig = {) ^* ~/ ^; A. z# ~
- number: number;
# r, W2 T7 j# H5 p - address: Address;
8 p8 P( C5 ]% r1 M: t - owner_address: Address;
% Z/ ~ |7 W4 U& e - };
% b& T$ A+ R. J& s. c- M
% m- k# Q. z- u' N6 s# Y- export function mainContractConfigToCell(config: MainContractConfig): Cell {
. [" J( R* |" p: R" q6 A3 ]) I6 T - return beginCell()4 c P9 m v% `, C( z7 }# F9 H
- .storeUint(config.number, 32)& u* Y& u& i$ m3 n3 x B: y
- .storeAddress(config.address)
$ [0 E8 W3 q0 A. s) g, _2 n - .storeAddress(config.owner_address) u' i1 b1 x, z8 q
- .endCell();- o: \6 V' ~; H0 E$ l5 I
- }
' c- v: G. w& z2 K7 E - 5 v/ ?$ h8 J8 d+ ~& g+ h
- // ...contact wrapper class
复制代码封装文件中还有一点需要更新-getData 方法,这样它也会返回 owner_address 我们还将创建新方法 getBalance,以测试存款后余额是否增加: % s. i2 A* s' r4 [3 B# N6 g
- async getData(provider: ContractProvider) {
s, e+ ?8 n; N. F4 s) Y, U* b - const { stack } = await provider.get("get_contract_storage_data", []);1 _& W! `4 ` x& g1 Y0 i+ y+ Y
- return {) d7 X6 O9 M6 L; m
- number: stack.readNumber(),
. F' M) z4 L* K. {- | - recent_sender: stack.readAddress(),
: U; ]0 \' s; f/ S3 a; ^ - owner_address: stack.readAddress(),6 Q0 U2 h/ ?" \6 q
- };) O6 A! W# Z' I* A- `8 G
- }) H0 H! h( r* K3 o) n: N2 U% S2 z
-
6 J' \9 T$ A8 N! |& T - async getBalance(provider: ContractProvider) {7 Y, @$ l) ~, {+ S+ j, }, M
- const { stack } = await provider.get("balance", []);
) s9 s2 e! \0 A/ R1 e' O - return {
. E0 D- L. m% c4 b0 ~6 W' d) o - number: stack.readNumber(),. |# G' R9 P9 t+ Q% v5 g
- };
7 t" T" i4 u; e; g/ I4 k - }
复制代码- // ...library imports7 v2 H9 @& n4 e' ]8 G
; W- P3 u1 X, E5 q- describe("main.fc contract tests", () => {
. z* q8 i h5 P3 G/ U, }: l - it("should get the proper most recent sender address", async () => {
6 W/ E# E( r6 M5 [ - const blockchain = await Blockchain.create();2 q" ^6 u7 J G1 r
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
4 M) q+ f) j U
* B1 p: j& l& `: \% s- const initAddress = await blockchain.treasury("initAddress");2 p+ o0 c$ F1 @' P" Y" C
- const ownerAddress = await blockchain.treasury("ownerAddress");5 L! P; \" |$ s
-
3 w# }3 ~( t5 i1 R+ } - const myContract = blockchain.openContract(
: y) Q7 g8 U/ L; _# P7 X- B% f - await MainContract.createFromConfig(8 l" R8 K: w7 I' k+ T6 H4 X+ {
- {
8 |$ c" O' h' O8 O! @ - number: 0,
# I2 y5 f, Y4 x+ g! w - address: initAddress.address," t, G2 m9 {5 A( e0 ^
- owner_address: ownerAddress.address, // now we create myContract from 3 parameters6 V7 I B1 @7 p! q, c. N% Z
- },
- D( w+ v7 {8 x# M$ k - codeCell" \4 f( G0 u {' O# L
- )
+ C# H* o: e7 e2 z# ?$ m$ k - );" \( q- P7 Q% T/ j4 j" y3 @
- 7 F4 {: I1 W6 w
- // ...rest of testing code
复制代码
9 ~7 w8 W0 F! K4 N8 [3 w我们引入一个新的金库,它实际上将是合约的所有者,只有这个所有者以后才能提取资金。这些更新在 tests/main.spec.ts 中进行: - // ...library imports
$ L4 |9 l* s& a1 @- M* U! D - & A' _" [) b% J9 D
- describe("main.fc contract tests", () => {" s' I6 t. g5 T0 t
- it("should get the proper most recent sender address", async () => {
& _3 p, a* i8 W% ^2 ^9 n3 j9 ^ - const blockchain = await Blockchain.create();& @; U8 G5 u. i1 \# n+ X, x
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];! d4 E; T) E; O% s
- 4 X: g& Z( i& S2 g+ m4 G
- const initAddress = await blockchain.treasury("initAddress");
) G o9 V: N" i; j - const ownerAddress = await blockchain.treasury("ownerAddress");" \& i" {+ i0 [- ]$ v1 n1 Z4 K
- $ R# d! Q1 w4 r- S: X! `/ ]
- const myContract = blockchain.openContract(& c9 O" N) a2 c2 h/ d% q' Y. e/ P ~
- await MainContract.createFromConfig(9 I( D1 N8 A5 M9 o# H2 p: F& i
- {
& W9 @$ ^" J5 B( Q% K u | - number: 0,' n4 H U' h0 R- b- _+ B
- address: initAddress.address,
8 W: v$ L0 D- }! x5 [ - owner_address: ownerAddress.address, // now we create myContract from 3 parameters4 C9 h5 x$ i. n: h# p i# n& s5 [: p
- },+ e- @9 h- Z0 F! E
- codeCell
! x1 F- E; e7 C, ~6 `' g9 O* p - )4 k+ O, } N2 ^
- );! }* Z8 l4 ^- N8 y: a; T" f
5 Y9 ]! ~7 [+ D M& s( x1 e- // ...rest of testing code
复制代码现在我们好了。 yarn test 成功通过我们之前编写的测试。
1 ]( G/ l5 Y" E* ?/ n' C存款测试让我们在 main.spec.ts 文件中再添加几个测试。 由于我们必须在每次新测试前重新启动合约,因此我们还将把合约启动引入 Jest 的 beforeEach 函数: - // We need to additionally import SandboxContract and TreasuryContract# n8 @! M& Y1 b- }6 t S; E7 F
- import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";# S0 f( M% N! B& [, U* [+ H; L6 R
- // ... other library imports3 U" Y4 q6 e- D2 l: d' t
- describe("main.fc contract tests", () => {, H8 g) l5 m/ \* I
- let blockchain: Blockchain;
9 j1 |% S- F) a( n - let myContract: SandboxContract<MainContract>;
: E# D2 L6 P, r6 P - let initWallet: SandboxContract<TreasuryContract>;% S& [$ P4 h. x" @2 r( X
- let ownerWallet: SandboxContract<TreasuryContract>;
. D* C- N1 D8 Q& ]6 T' S6 i8 g3 E
9 |4 X8 V/ s- @& ?- beforeEach(async () => {
' F8 v5 C) k, F - blockchain = await Blockchain.create();3 O& W4 C6 p- i$ G: G
- initWallet = await blockchain.treasury("initWallet");* o9 h7 X( n8 _* ?% S: h
- ownerWallet = await blockchain.treasury("ownerWallet");1 c) s* W5 X0 [- u3 o8 T) [
4 T9 k1 V1 B' s, S% |- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
; j) h2 d5 m- @) Y7 {7 | - - w e5 t4 B! v$ _; O; s7 U
- myContract = blockchain.openContract(- H! _. j& Y1 u* m3 g4 }+ `& V
- await MainContract.createFromConfig(" z& b3 F4 [/ m- S
- {
" B" B# @0 B' T - number: 0,3 h7 C" ~: H3 K3 c; B
- address: initWallet.address,
. z$ E4 g, R9 c* v- T1 I - owner_address: ownerWallet.address,( H3 G) W4 R* v3 G& O6 L" U
- },. `# s3 w( \2 `8 ]
- codeCell+ L- n+ c) q% n: z; X+ E
- )* n3 C' ^" a+ ?
- );
6 C& g0 M: T% x7 X/ l. } - });
6 ?- z4 Z% ^) P& c5 ~3 f - 2 _8 F |: A( m9 c* Z3 D& M
- it("should get the proper most recent sender address", async () => {
0 K: [- }( g8 R2 r9 [. c - const senderWallet = await blockchain.treasury("sender");
7 o9 G$ R# H. u% S
7 k# }9 h; J" _, \; }7 r9 x v# I- const sentMessageResult = await myContract.sendIncrement(
+ u" h, T6 F1 H2 }' o2 P/ x - senderWallet.getSender(),) H+ j/ ]* ]( w: z( p
- toNano("0.05"),9 p6 i8 k- S2 g3 f
- 1& k* f! H* \2 d
- );
2 L4 k; O+ U- M- R - " p: _- L+ P1 G$ o& q; ~; ]
- expect(sentMessageResult.transactions).toHaveTransaction({8 J3 ^: o' ~9 r' L! M1 q) R( _
- from: senderWallet.address,; A5 f6 W6 W: j/ O5 L2 F) b
- to: myContract.address,6 W3 |, ^( z" e$ r2 C1 {
- success: true,
r. c0 Y6 K+ M$ i* U1 `0 b - });/ C( m1 L6 ?, ~* {& c; s
; Q- Q. F0 I9 {/ e* q- const data = await myContract.getData();
$ H6 M* l6 D0 X: x0 C s+ g2 [ - 6 @! f0 l3 T7 g$ B7 F' _+ t
- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());
% }! }: k- W) Z7 E4 Q7 H. V - expect(data.number).toEqual(1);
' I) e/ ~+ i1 Y9 w# U, K$ ] - });
: D8 _$ _2 i8 V2 j h7 I - it("successfully deposits funds", () => {
" c% a( I/ n) Z/ ]( u* @ - // test logic is coming9 j( A& x3 ~# K, d& i& P! ^- p
- });# G7 b* a; C1 R; S
- it("should return deposit funds as no command is sent", () => {
! R; q& p2 ?! T) B4 d* y - // test logic is coming
! i6 A. K$ g7 b: E+ O! M; F - });
& F2 o( i8 \( w+ m - it("successfully withdraws funds on behalf of owner", () => {9 s# g" h* h* M
- // test logic is coming
. o P0 `' b* K - });5 n! n4 T7 C6 B" {: Y( e! s4 `
- it("fails to withdraw funds on behalf of non-owner", () => {
2 H2 w/ X' `, i. n% w - // test logic is coming
) z- q3 g% A. o1 S+ U - });9 y& e9 G) D$ K
- it("fails to withdraw funds because lack of balance", () => {
3 L$ t0 C% C! s7 ?: i7 ?% e! O$ t - // test logic is coming0 p5 e, ]7 q8 G
- });4 e+ k- F5 ~1 l1 E
- });
复制代码我们只是将第一个测试的部分行移动到了 beforeEach 中,其余的行应留在第一个测试中 it
让我们为存款写一个测试。我们将发送一条 op == 2 的信息,然后检查余额。像往常一样,首先创建一个封装方法,我们将其称为 sendDeposit: - async sendDeposit(provider: ContractProvider, sender: Sender, value: bigint) {$ D4 B3 e8 F* k. j
- const msg_body = beginCell()
. z% ?2 m- d. I) l - .storeUint(2, 32) // OP code" X o. ?+ J6 @' L2 Z% Y
- .endCell();4 n; E' o- ~1 Z% K8 Y! V
- & d! d) {! s3 s! o
- await provider.internal(sender, {) E* j! h4 p6 m4 U+ G: D7 E, a
- value,
! B+ v. E6 ^3 N) R# N' I - sendMode: SendMode.PAY_GAS_SEPARATELY,3 S7 K1 E) D: L3 `8 |% Y! e0 n
- body: msg_body,
& u9 [6 Q* u0 a$ z% t( w - });
) |# B7 b, q+ ~! g - }
复制代码我们将不详细介绍这种方法,因为它非常简单--我们只需在信息正文中传递一个操作码。 下面是我们如何使用该方法运行存款测试。 - it("successfully deposits funds", async () => {
' E) `# L" t) N( B - const senderWallet = await blockchain.treasury("sender");; a6 c' k9 O" Y2 s
- ) l( G3 u2 |# G: O5 K: Q4 a. s
- const depositMessageResult = await myContract.sendDeposit(; i% U, E- z3 G) G$ y# v2 R% [
- senderWallet.getSender(),% ?" p# x. u0 z+ W4 H
- toNano("5")# x! J9 { K8 P
- );
; Q: ~: `' ?0 B6 Z
/ B. d3 m& Y3 H- expect(depositMessageResult.transactions).toHaveTransaction({* j( ]# D* ]9 @: h1 D
- from: senderWallet.address,. r6 d g. g6 Y5 [# u9 [/ s# V
- to: myContract.address,
6 g5 t! j. Z) f; S' |( [ - success: true,
7 N5 p1 I5 D* o/ K - });
! t! Z+ D. V8 y' V" \
7 V- Z- Y6 Q2 i8 ~0 Z& F! m- const balanceRequest = await myContract.getBalance();8 W" x9 ?/ d# M/ J, H7 j' U, I- W& m
- 7 {* g' @& ~! d- [
- expect(balanceRequest.number).toBeGreaterThan(toNano("4.99"));1 @5 H- D. L8 M$ y7 b: o/ A
- });
复制代码请注意我们是如何检查合约余额是否大于 4.99 TON的,因为我们知道手续费会损失一些资金。 我们再写一个存款测试,但这次会出现误报。我们将在没有存款操作码的情况下发送资金,并期待返回交易,因为该操作码未知。 和以往一样,我们还有一个包装器: - async sendNoCodeDeposit($ _: L* X4 y' p
- provider: ContractProvider,! D8 |+ V- E, [8 T2 g) r4 a# v
- sender: Sender,
" D- W8 h# x# f6 k9 p. a! Q - value: bigint
* |5 w, d2 q. K0 @: A- ^ - ) {7 f# L* j s* K6 m
- const msg_body = beginCell().endCell();# C& J. X$ q; d0 P
8 D9 [% I; } h2 G- M' U- await provider.internal(sender, {
5 j! E0 m% ? \- { - value,1 \* \# h* E1 x1 o+ R5 l6 H
- sendMode: SendMode.PAY_GAS_SEPARATELY,, {6 w( R |9 e$ o% U( H* ~* \ W
- body: msg_body,
. e; A3 g- h% u9 ~3 D# N - });
; _) q) ?4 }- o/ S - }
复制代码下面是测试代码:
; ^ h: R1 a2 n; e- it("should return funds as no command is sent", async () => {% b0 {7 w8 W5 V5 n6 A
- const senderWallet = await blockchain.treasury("sender");* J1 `7 P; Q0 [- l
) b9 Y& _: Z, T, e& V! `. }% ^- const depositMessageResult = await myContract.sendNoCodeDeposit(1 y& G) Y+ a: p
- senderWallet.getSender(),
' X# j2 b0 c$ `% n - toNano("5")) i: o+ B) k4 H7 ~" g
- );
$ s/ M' i- [3 Z9 ?) r( r7 W* G
% z, x; s7 o) O7 d- expect(depositMessageResult.transactions).toHaveTransaction({
1 N2 a" N, K2 H/ A8 n- ^ - from: myContract.address,. r* ]! f7 ` [$ u
- to: senderWallet.address,
3 g7 O# G! d6 `: x - success: true,
B( n4 |2 E$ V' k2 y - });
0 ~! q/ I3 P1 s N- W9 j$ i' N
( ~' i5 ^. y( _6 h+ [/ L4 B' k- const balanceRequest = await myContract.getBalance();
' X5 g7 G. L( n+ \- i+ T( |! I - 8 W( J& @- p" _5 i! a& l, ^0 r8 I
- expect(balanceRequest.number).toBe(0);
/ i' X) z" h1 t - });
复制代码 提款测试让我们先创建一个用于提款的新包装器: - async sendWithdrawalRequest(
' C$ I* ] x: D; T - provider: ContractProvider,* k# c/ x* t2 x o A
- sender: Sender,
- Y+ F5 B: Z+ o7 C# M! T - value: bigint,
7 i% W2 J( b) x' N - amount: bigint/ D+ }$ a5 y. K! c z. q1 e2 x
- ) {
6 r* L' J% {9 o( \( M6 r - const msg_body = beginCell() z( e) |' |. s. S4 h' h7 i5 T
- .storeUint(3, 32) // OP code2 `. [) V! g' R% p& C3 H% S/ N
- .storeCoins(amount)
+ @$ I: P- D/ c3 Y0 ~ - .endCell();. S) d8 r- J, H* V
- S2 k! R% ?, W6 a- K0 @- await provider.internal(sender, {) S. f$ W! @$ q" Z/ b$ I
- value,
) T& f, o3 O) S3 U, ^2 x - sendMode: SendMode.PAY_GAS_SEPARATELY,
3 P: Y: P2 L7 M% v2 ~$ k - body: msg_body,
' w( R8 ^, `8 ^0 K w/ N0 g1 t - });
! J3 K; O d- p$ J$ i5 S. T - }
复制代码我们输入适当的 msg_body 和所需的提款 amount 。 我们将进行 3 项不同的测试。让我们一起来看看: - it("successfully withdraws funds on behalf of owner", async () => {
) J4 k. C2 ^# m! W - const senderWallet = await blockchain.treasury("sender");6 Y" f, r9 e3 b
- % A+ j0 r2 A k8 `7 [8 i% w, U) T
- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));
2 u& J- U; [* ?4 p1 G - 7 ]9 F6 [! r, b) O- O
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(! ?5 U! x" m, c( ~
- ownerWallet.getSender(),$ L" Z1 `) B7 m. i
- toNano("0.05")," t' K4 ]7 z# P9 w9 ~% M4 o+ z
- toNano("1")
{. w8 z1 Y9 Q& ?4 ` - );3 J6 Y6 R0 f! J! o# h* C3 O6 t' ^
- 8 }1 X& p4 O; ]5 g
- expect(withdrawalRequestResult.transactions).toHaveTransaction({
6 X: ?" d& v' |) | - from: myContract.address,6 n* Z0 e3 [9 k& B
- to: ownerWallet.address,- }0 c) S- C1 s! u! o
- success: true,' u: M3 k- @- i
- value: toNano(1),, s9 T- u3 l" D) O5 N
- });
/ A! ~( R! i$ T+ N - });
复制代码 要成功提款,我们首先要正确存款。然后我们调用 sendWithdrawalRequest 指定我们要提取 1 Ton。请注意,0.05 Ton只是为了支付费用而指定的信息值。2 c! b/ H) u$ z- V
- it("fails to withdraw funds on behalf of not-owner", async () => {
" f, |5 B" n U3 [$ ^ } - const senderWallet = await blockchain.treasury("sender");/ ?/ G. O( g9 S' \) f2 D
- 4 [8 j E- Y, o9 z& Z
- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));
: a J9 I" e6 z- P - ; E( k- m/ q8 U5 }& P
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
c0 f" E/ \- x - senderWallet.getSender(),
* ?8 u7 [7 X! |. u% \2 h; l, q - toNano("0.5"),3 m, n; t: F* I# M2 `' M
- toNano("1")7 B( V) c4 X/ B3 q9 ]
- );
7 f. r$ [8 k& f; d
6 t$ ^% P$ q% }5 Z- U- expect(withdrawalRequestResult.transactions).toHaveTransaction({% F/ J4 R9 Z4 l8 r" z* L
- from: senderWallet.address,2 @* P* \' w4 |; ] |4 ]5 }$ z t
- to: myContract.address,3 C. c( q: r# \& I6 m% w+ d+ Z5 ~
- success: false,
" G) d, d4 X6 k; x+ o8 D5 v - exitCode: 103,! f2 L- g, ~5 J* H& {8 ?
- });
4 O8 q% I% P3 p1 m! r3 q7 P/ ` - });
复制代码在本次测试中,我们代表发送方钱包而不是所有者钱包发送提款请求(两者的区别在于初始化时我们提供的助记词). 还请注意,我们是如何通过 success: false 和具体的失败原因 exitCode: 103. 为了确保我们做得正确,请看一下我们的合约代码,我们有这样一行代码: - throw_unless(103, equal_slice_bits(sender_address, owner_address));& T; f1 Q0 u" ~
复制代码我们进行最后一项测试--由于没有余额,提款失败: - it("fails to withdraw funds because lack of balance", async () => {" K) M1 P, E" ~( }* Y
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(/ x; A; M! ^! g' P& h' O# [; w. n
- ownerWallet.getSender(),+ j3 |% z# {( |8 w" N2 O7 H9 U& s
- toNano("0.5"),: b% }2 U# s. u
- toNano("1")% F, o' S1 F% U5 z0 l) b
- );
4 P" s0 i& k- ^7 D4 F6 q - 5 ?, C; N( {3 J
- expect(withdrawalRequestResult.transactions).toHaveTransaction({! ~; ^3 R7 t, Q: r. ` u1 c
- from: ownerWallet.address,
, u6 h0 [( {2 Y5 a- ~/ }6 ?- z - to: myContract.address,5 R+ F) `4 \$ k0 @& h
- success: false,
5 K8 c, T0 y. `" y$ k - exitCode: 104,
) V; L5 S- o6 ]/ J, W - });4 E+ [5 a, L8 X( I* Z7 Z b. t
- });
复制代码- throw_unless(104, balance >= withdraw_amount);
1 J; x* m! q9 @& _" t: o/ H, f
复制代码就是这样 几章之后,我们将学习如何构建网络客户端,以便与合约进行交互。 , G. G4 _8 T' }
5 D7 y7 O% [0 s5 h: G% | |