在本课中,我们将编写一些有趣的测试。 首先,我们要更新之前编写的合约的初始化过程,因为我们引入了一个新变量,它应该存储在 c4 存储器中。 9 - Cell下溢。从片断基元读取数据时,试图读取的位或引用数超过总数。 更新我们的合约包装器首先,让我们更新我们的合约包装器,以便它能意识到 c4 存储中的第三个参数。也就是说,我们将更新 MainContractConfig 和 mainContractConfigToCell 函数 - // ...library imports3 x1 x% O' _ l% P! a
- ' D% K+ m% B/ N6 Z* y* r
- export type MainContractConfig = {, L8 m, H# t h* B
- number: number;9 ?. N. o1 k8 |4 ~% r }
- address: Address;$ s9 P- c9 ^! ?: g* L
- owner_address: Address;; w( V) ]' ?* N: m/ ]
- };2 F, v: J) y8 o$ T( Q2 B& v" A
- 3 F: w" ]( Q. c5 z: F
- export function mainContractConfigToCell(config: MainContractConfig): Cell {9 y2 J8 N3 }/ R# C
- return beginCell()
/ ~ o: g' j/ r - .storeUint(config.number, 32)0 }9 R- y: c6 s- f* u
- .storeAddress(config.address)
1 H8 ]. J2 n! [ - .storeAddress(config.owner_address)
+ d* i& Y% a" ~" s - .endCell();0 _2 }2 e% `9 e. K8 d" ]) @" c& `
- }/ H! ?2 ~& Y- a) X1 ]
- + i" L" o& d" e1 X9 ~( i
- // ...contact wrapper class
复制代码封装文件中还有一点需要更新-getData 方法,这样它也会返回 owner_address 我们还将创建新方法 getBalance,以测试存款后余额是否增加: / w7 K( S1 @9 V9 n$ F, v
- async getData(provider: ContractProvider) {* g0 u' g4 A, r# Y# W7 a4 P: ^
- const { stack } = await provider.get("get_contract_storage_data", []);
8 N# D0 R* ^) r% N - return {
) s% [+ u" b2 O# x' \ - number: stack.readNumber(),
5 u8 L+ \; E1 @# ?% e) [, h' Q$ j - recent_sender: stack.readAddress(),4 ?: L! e, H+ o. }
- owner_address: stack.readAddress(),
& j% C- t& e8 \ - };. v' @* I4 x' {. {' z8 f
- }0 n/ S5 ^9 m. `4 M5 `: _. s
-
( n: y9 h2 Y- F. O9 Z. w4 b2 i1 Y. ] - async getBalance(provider: ContractProvider) {" W$ w( q7 a* m* V4 q% |5 _
- const { stack } = await provider.get("balance", []);
v9 Q F; Y' a. n8 R. n' i% h. U - return {; i0 v& z% }# a4 P; e
- number: stack.readNumber(),
, e) F, `* L* X - };8 a& _, I; n. X1 C: }
- }
复制代码- // ...library imports
5 P) \+ |0 _! Z- A1 g: a - ! ?& A' J! t, w1 F
- describe("main.fc contract tests", () => {) j% o& R0 c. S2 C6 ]- f7 S5 y
- it("should get the proper most recent sender address", async () => {
( g+ x- X% [" X1 V9 C - const blockchain = await Blockchain.create();
8 H: j# P% {+ e% J# s+ t& k# Q - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
3 k9 Q! L% _( }! t. a: L$ i/ T - 3 h" g' \. c6 u8 o; Q& c' `$ I+ P
- const initAddress = await blockchain.treasury("initAddress");9 x; b: C1 \1 d) v0 u2 Q
- const ownerAddress = await blockchain.treasury("ownerAddress");
5 N0 T; g; H4 {' C# ^# I3 B -
+ w [& O9 u* y- O3 Y3 N L - const myContract = blockchain.openContract() b# U5 V }; O) ~( ~" l
- await MainContract.createFromConfig(
# P2 W5 a' X4 G2 a. w0 u - {+ u- R. V- \" n. l; \0 H9 \8 b! r
- number: 0,
. b( m l- n b - address: initAddress.address,& p9 W/ f2 ^7 H, g
- owner_address: ownerAddress.address, // now we create myContract from 3 parameters) p4 |; S" @4 _; ?6 m, |
- },2 o5 Z+ z5 w' S) p6 h8 u
- codeCell
$ z2 X4 u; K/ T, @; K q4 I1 j - )4 l3 ?1 v f# Y8 G+ U
- );( X- o) R# N t& X; |9 x0 Q( G( }
- ( n) a0 n2 f! ~1 b. u9 `- g; t' \
- // ...rest of testing code
复制代码
! g! x3 I2 |" G, ?+ i我们引入一个新的金库,它实际上将是合约的所有者,只有这个所有者以后才能提取资金。这些更新在 tests/main.spec.ts 中进行: - // ...library imports* H3 x" ] `+ _1 C& v8 V. `) x
; w, ]9 P# h1 W. L( |- describe("main.fc contract tests", () => {9 O. z# b' E2 h- `+ c
- it("should get the proper most recent sender address", async () => {
2 U8 B5 \+ |1 o- F( n - const blockchain = await Blockchain.create();
; F2 F2 Y1 r9 B( J - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];8 f! C5 m$ Q3 g$ T: P6 Y9 n( n
- 3 M# E' R' I! r! C* R" N d# O
- const initAddress = await blockchain.treasury("initAddress");9 h" f0 E$ ]; B) [% y9 v- e% K- A
- const ownerAddress = await blockchain.treasury("ownerAddress");
, o4 V- z* n3 s6 R m. l; V -
" f( b9 |. M8 W( V ~; [ - const myContract = blockchain.openContract(/ r( b: n1 z. ]% g. v8 u
- await MainContract.createFromConfig(" O( c$ l: l }- T; j( z
- {
* l. ~% e% V) G! m( H- A; p - number: 0,
- i' l( N' ~* {3 A - address: initAddress.address,8 u0 X9 ]' p& P4 U
- owner_address: ownerAddress.address, // now we create myContract from 3 parameters
0 h5 r" K/ t3 d+ V( D/ ` - },; ?/ A. l5 N9 a! l
- codeCell
% B- d4 ~* K: A/ g3 U/ C! M d - )
1 f, _+ r' g- Q' _$ T - );
5 d$ g+ F W8 W' Y% \ - ' ]) R1 X: d* E8 l5 h
- // ...rest of testing code
复制代码现在我们好了。 yarn test 成功通过我们之前编写的测试。 * n" X5 c: R( i0 T
存款测试让我们在 main.spec.ts 文件中再添加几个测试。 由于我们必须在每次新测试前重新启动合约,因此我们还将把合约启动引入 Jest 的 beforeEach 函数: - // We need to additionally import SandboxContract and TreasuryContract7 K% X1 S7 n1 H% v1 m% u4 {( J
- import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";2 @, e3 f% ]0 m( s) P, I/ K$ S
- // ... other library imports( Y" R' x1 A6 Z5 L
- describe("main.fc contract tests", () => {
) N% x: E1 W% O- j4 O - let blockchain: Blockchain;3 x. B3 o4 h$ `2 |: p$ V
- let myContract: SandboxContract<MainContract>;
: R. r. X. X8 ~6 j - let initWallet: SandboxContract<TreasuryContract>;
* X6 t4 p' T' S7 t6 x+ Z6 m$ ] - let ownerWallet: SandboxContract<TreasuryContract>;
8 O2 u9 B/ H6 E3 C% i! D4 S - ( S4 x8 m# Z" I0 W) E& G
- beforeEach(async () => {
6 p: N; O& ]& Y' E' s8 e7 u - blockchain = await Blockchain.create();
' Z6 w2 \3 Z, u- G* c( V4 g1 O - initWallet = await blockchain.treasury("initWallet");2 w0 G. n/ A" G: v% G6 G ~
- ownerWallet = await blockchain.treasury("ownerWallet");* r# M5 ^. t" _5 u) h+ M% S
) k+ x- \& C: N3 D# d' B% @- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];/ R- k5 ?' ^: x p. f
- 0 J0 Q8 }3 N$ X. r. {/ B
- myContract = blockchain.openContract(' p N# b. l/ Q" o% u# L4 B
- await MainContract.createFromConfig(& s7 v' ^, @1 s3 k
- {" c, s' O6 v {* U
- number: 0,& n4 F. I/ k0 L. G/ q+ Q% m8 d
- address: initWallet.address,8 J5 I6 E) A1 u( X) k9 o) v% Y
- owner_address: ownerWallet.address,8 y% ]) r( ^$ x' _/ g
- },
% c5 z. t3 o6 ~/ l2 K - codeCell. h3 i! ^1 s+ |# u9 N) t
- )
T$ E* ^- a/ A& c2 x. T" [+ l - );: u4 X; I! T( c. @
- });6 A, o8 m6 p( Q) F( C
7 V5 I# _( f4 E2 D4 J" X; d- it("should get the proper most recent sender address", async () => {
8 `) v. M! `, E0 S - const senderWallet = await blockchain.treasury("sender");
T' u' k! H; z5 g z' X2 w
$ S6 v1 `" \! ]- const sentMessageResult = await myContract.sendIncrement(
3 s$ b5 i+ |6 A8 l1 ` - senderWallet.getSender(),
' a" k5 P- b' i! _$ x" P: C - toNano("0.05"),2 ]) U2 R, r& L9 [
- 1
! Z9 P" S; J0 X - );
2 t" J7 D3 i5 r O
, h$ k1 V3 r& q2 ]4 q/ m- expect(sentMessageResult.transactions).toHaveTransaction({1 b* X) }# C# b8 z( `8 s% f! k1 S
- from: senderWallet.address,$ D4 ]! B! n; q
- to: myContract.address,
4 ]. A9 A+ ]- g3 l; R9 K - success: true,
4 f2 k" c- P& J# W% B# S. i - });% k! x8 l5 u7 R: b: S! f& i! S
0 f0 V$ T* E, F: J- }/ K0 a9 A% X% ?- const data = await myContract.getData();. c$ G% u# Y( b7 ^; B
- * U0 d/ j+ F* ~6 W% Q1 u
- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());) J; a& y8 [9 ~7 i
- expect(data.number).toEqual(1);6 C$ U" U3 B/ N9 e e3 b, i
- });
6 Y4 a6 Y% |0 _ - it("successfully deposits funds", () => {$ ?& i `& E' @# a
- // test logic is coming& O& ?- Q, F4 }
- });! r+ d4 l' {) u! G- D) \
- it("should return deposit funds as no command is sent", () => {: S/ J5 @! s5 E2 ]; P: z
- // test logic is coming
7 C/ z0 g2 y# I* ^, m9 h( U; J* Q - });
+ o. x) d! W+ n% U, [3 u* M/ i - it("successfully withdraws funds on behalf of owner", () => {+ }/ v* K) s! Y) z% O+ ^2 N' ^
- // test logic is coming+ Y$ a" x; o/ {5 i6 u1 f) u- k5 A
- });
8 u" N" l8 z9 w9 e. E& q4 s - it("fails to withdraw funds on behalf of non-owner", () => {
' I$ U+ N7 O# }) l2 T - // test logic is coming+ H* r: u( c$ y5 c8 u8 T
- });+ f) k3 o# E+ @. h( @
- it("fails to withdraw funds because lack of balance", () => {
3 }1 V& ~/ j! R3 O% `8 E/ u - // test logic is coming& O/ ]5 I6 P3 T) K' S
- });
' z" y! I- o# e7 V8 k. ] - });
复制代码我们只是将第一个测试的部分行移动到了 beforeEach 中,其余的行应留在第一个测试中 it
让我们为存款写一个测试。我们将发送一条 op == 2 的信息,然后检查余额。像往常一样,首先创建一个封装方法,我们将其称为 sendDeposit: - async sendDeposit(provider: ContractProvider, sender: Sender, value: bigint) {/ U$ s/ I9 A9 |" G+ e
- const msg_body = beginCell(), E F, ~1 L+ B* \
- .storeUint(2, 32) // OP code
, {6 w- a3 b* Z& Z - .endCell();) i, }1 _9 i# B& n
- 7 T; m6 @( h: V8 `; \, \3 I6 ~
- await provider.internal(sender, {: u3 G$ h2 T: O2 A; Z: i1 z
- value,0 x0 j/ l; z) k, f/ ?0 {5 l; I; u
- sendMode: SendMode.PAY_GAS_SEPARATELY,
/ U k# J& G1 B' b% D5 G& T6 A - body: msg_body,
- Z$ o H8 I& r: V4 \9 o. K: b N - });
3 Z' g% ]1 {# V' @ - }
复制代码我们将不详细介绍这种方法,因为它非常简单--我们只需在信息正文中传递一个操作码。 下面是我们如何使用该方法运行存款测试。 - it("successfully deposits funds", async () => {
# R9 d. h7 p. f4 O, c3 q0 o - const senderWallet = await blockchain.treasury("sender");. E# B& W% n, M2 V
0 q. B1 G! h k' V4 b- const depositMessageResult = await myContract.sendDeposit(
/ T6 r% C$ t3 L) t* B8 H - senderWallet.getSender(),. Z4 o Z' @7 J" M$ S+ _7 N
- toNano("5")* y7 J4 b. \6 o( M4 J: Y
- );
* }$ f3 T$ g5 k" V* w
- Q" O7 E% t% W8 G- expect(depositMessageResult.transactions).toHaveTransaction({
( l, k- U* E! B$ i% ~# q8 ]$ B - from: senderWallet.address,1 c+ q( { i! Y6 O
- to: myContract.address,
3 C: j3 e% @; C$ f - success: true,0 E3 `$ o' [5 o7 `. D' i' Y: i% C
- });& M9 R; L1 ?2 i( c
- 8 f. w a Q5 V/ O4 _
- const balanceRequest = await myContract.getBalance(); E( c1 N- \5 P# E& d
- 4 A# b5 ]" u+ _! s
- expect(balanceRequest.number).toBeGreaterThan(toNano("4.99"));
6 o4 A3 H2 M E0 R' D - });
复制代码请注意我们是如何检查合约余额是否大于 4.99 TON的,因为我们知道手续费会损失一些资金。 我们再写一个存款测试,但这次会出现误报。我们将在没有存款操作码的情况下发送资金,并期待返回交易,因为该操作码未知。 和以往一样,我们还有一个包装器: - async sendNoCodeDeposit(4 c9 S: q3 P: M: F3 G+ ?
- provider: ContractProvider,1 s0 X$ P% t0 C( E. k) n
- sender: Sender,
; l/ U" y) ]0 s - value: bigint* Y2 a8 B; B7 R5 d9 w
- ) {9 b- g w% [# M( q R
- const msg_body = beginCell().endCell();
7 L7 f! Y0 D% w* c - # O1 X9 L" _3 F( J
- await provider.internal(sender, {( b' F9 o$ c& ~8 N% k
- value,# T- Z8 B3 ~0 c: g
- sendMode: SendMode.PAY_GAS_SEPARATELY,
/ D5 C1 N1 |) V' w9 \, [! N - body: msg_body,
3 l) Z2 q% B1 N8 K4 H$ [. r6 v - });9 C. N0 d( R4 z8 m
- }
复制代码下面是测试代码: 2 j2 d4 t) i6 _3 w& R3 c' r
- it("should return funds as no command is sent", async () => {) h! ]3 r. l. N) l
- const senderWallet = await blockchain.treasury("sender");
2 W5 C3 p' p2 m' @
* D3 L# W9 q" c( z- const depositMessageResult = await myContract.sendNoCodeDeposit(+ b* {9 O" Z3 ^, g
- senderWallet.getSender(),
2 I/ |) g" U# ]+ C1 a* E8 ` - toNano("5")
0 {/ L! y+ X; A. v! P - );/ `9 y1 T% p0 f, ]3 Q2 y) f
- B' \3 H: @% O( T
- expect(depositMessageResult.transactions).toHaveTransaction({* [, H" ], S$ R+ d0 e" n% ?! t2 Y
- from: myContract.address,
- ?6 ?* e1 Q O, @ - to: senderWallet.address,: x) F+ L$ h% w& f4 V3 ~8 X4 @5 t$ y
- success: true,
3 T$ p/ @# u1 u, B - });+ g, D7 M- R7 Q& k' N7 B7 `; A
5 U( L) |# \4 n: \+ H- const balanceRequest = await myContract.getBalance();# N' q" J- |) w, }: Z+ L0 [
0 c/ \) g: D; W& K2 f, A- expect(balanceRequest.number).toBe(0);! s/ A! f: W5 A3 ` }
- });
复制代码 提款测试让我们先创建一个用于提款的新包装器: - async sendWithdrawalRequest(, y- f0 ^- H: R, M$ Y
- provider: ContractProvider,! x2 Y5 k$ k; r, ^
- sender: Sender,
( m3 L* @4 p9 ]* R3 v4 M3 e - value: bigint, r, T. a3 X1 U7 A& j% L/ b N
- amount: bigint# y6 S4 K" G+ T" C. W5 z. O8 ?
- ) {; D* h2 x$ j0 x% t
- const msg_body = beginCell()* E. _/ G5 p7 s. @ d
- .storeUint(3, 32) // OP code
2 ?" t I) S1 G/ Z+ A) q - .storeCoins(amount)
4 w5 ]1 g0 c* E* Y' j& U - .endCell();$ L, c# E$ `7 P
E- d; J6 c6 T) b- await provider.internal(sender, {
J9 t9 d; \0 @7 @) Z. \3 I+ d, d - value,
1 p2 i5 U& n4 C. k1 X* B - sendMode: SendMode.PAY_GAS_SEPARATELY,
: z$ W, `' X' Q$ v+ {$ {/ z- @ - body: msg_body, J5 `6 \4 ?. Q
- });
& i2 ]) f" I8 N) h- |/ Q - }
复制代码我们输入适当的 msg_body 和所需的提款 amount 。 我们将进行 3 项不同的测试。让我们一起来看看: - it("successfully withdraws funds on behalf of owner", async () => {/ N& L+ Q1 Q, |2 W: A$ m7 u
- const senderWallet = await blockchain.treasury("sender");
, M8 {) q3 _. y/ |1 q" Z* l( f9 U
4 c2 a+ g' L2 r4 b3 b, K- c$ J- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));. j. |4 _. Z9 h6 y- i" y% }# w
' F, n! `% r( Z$ {! N7 x- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
2 N# Q" |! j% m1 t, F' R9 @ - ownerWallet.getSender(),
) R, `% V, _ R6 Y9 b4 A - toNano("0.05"),
! d$ p( l, @: l1 Q+ l: a - toNano("1")$ d0 Y' ^ d+ E4 g8 W% o
- );! G, n) f! y1 D# h* t7 `
8 Y) W) e' T- r7 T- expect(withdrawalRequestResult.transactions).toHaveTransaction({3 B- m B: V6 X. f4 q
- from: myContract.address,
2 Z3 y0 f9 z# M( G - to: ownerWallet.address,% t0 |7 k K% ~3 n+ p
- success: true,
: T4 \ b- n; a, D3 J* B6 @ - value: toNano(1),
; H: r9 Z/ L0 t3 F8 ~ - });
( ~4 R& k# ~$ m, |! l( F - });
复制代码 要成功提款,我们首先要正确存款。然后我们调用 sendWithdrawalRequest 指定我们要提取 1 Ton。请注意,0.05 Ton只是为了支付费用而指定的信息值。( r* B( R: o7 f, c. s
- it("fails to withdraw funds on behalf of not-owner", async () => {9 }( P$ h) @! o D* z( g2 X' d4 p" E
- const senderWallet = await blockchain.treasury("sender");
! {7 L( q: l; A* X5 P - % @7 L* ~. X# t* o8 u m1 y" ]
- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));8 s" {4 R0 O1 w# L. Z( q$ ^; _( p
- n* x4 ]& U* I- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(5 V9 H/ q, A. k
- senderWallet.getSender(), n. [( v5 l8 h! [6 N: S
- toNano("0.5"),) ?- R" p( I. l
- toNano("1")
x8 C! w% F; @) a7 A/ y - );: a. h0 _( o, ]1 }; S; r
2 E. l! s+ g* V- expect(withdrawalRequestResult.transactions).toHaveTransaction({
y$ B; D* W+ [ - from: senderWallet.address,; B! [/ p( t" l T& M
- to: myContract.address,9 B D' R7 a% S
- success: false,7 q, P6 U/ {6 L4 }
- exitCode: 103,: \" [/ z4 D' F$ l' t4 m+ l: `
- });
6 y4 i! _' O7 |% _% r3 z - });
复制代码在本次测试中,我们代表发送方钱包而不是所有者钱包发送提款请求(两者的区别在于初始化时我们提供的助记词). 还请注意,我们是如何通过 success: false 和具体的失败原因 exitCode: 103. 为了确保我们做得正确,请看一下我们的合约代码,我们有这样一行代码: - throw_unless(103, equal_slice_bits(sender_address, owner_address));
4 F7 A; Y7 h! S; u) R
复制代码我们进行最后一项测试--由于没有余额,提款失败: - it("fails to withdraw funds because lack of balance", async () => {
! l, }# P) j' ]; @. G, ~9 w1 m - const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
) z8 h9 i, F# Z - ownerWallet.getSender(),9 G6 E0 |' x5 r. N; S- }
- toNano("0.5"), i- p4 D1 q* y+ v- X
- toNano("1")- o6 J5 l( L, w; [8 |
- );2 M% O2 _5 X) n8 Y" X
+ A$ m" j* A4 N; j7 E n3 W- expect(withdrawalRequestResult.transactions).toHaveTransaction({
$ M- P$ k3 h2 J - from: ownerWallet.address,
, q1 l" j- U6 b; V - to: myContract.address,
4 L) Q7 {5 s" q% o u - success: false,! z* [! l# U& c, I1 D/ E
- exitCode: 104, M( |6 n, k# o$ V: t
- });3 K, x" ~0 L4 C4 [ q$ ?3 K* b
- });
复制代码- throw_unless(104, balance >= withdraw_amount);2 D1 n. c' `! o7 H5 G
复制代码就是这样 几章之后,我们将学习如何构建网络客户端,以便与合约进行交互。 + f, z# d6 ?# H; X% ^" n# d+ C6 }0 Y
$ ?3 j3 Y2 y2 I6 G |