在本课中,我们将编写一些有趣的测试。 首先,我们要更新之前编写的合约的初始化过程,因为我们引入了一个新变量,它应该存储在 c4 存储器中。 9 - Cell下溢。从片断基元读取数据时,试图读取的位或引用数超过总数。 更新我们的合约包装器首先,让我们更新我们的合约包装器,以便它能意识到 c4 存储中的第三个参数。也就是说,我们将更新 MainContractConfig 和 mainContractConfigToCell 函数 - // ...library imports
9 R5 t8 M" _/ K( Z; W - - `+ Q9 D0 n! U) @7 {" ~
- export type MainContractConfig = {4 [" F# n: h$ \$ I+ s0 Y* L
- number: number;1 x/ V, K: S' t! K/ h
- address: Address;
2 D- ]( [' ^# ^/ W: l - owner_address: Address;: Q8 g' a4 ^1 D, i8 u* V9 b6 t
- };- D, x1 h" E% f3 O1 H
- ' B; E* C* V( M9 v% r$ t$ q4 _1 d
- export function mainContractConfigToCell(config: MainContractConfig): Cell {. s) n& f7 r* f0 t9 [! s/ L) [
- return beginCell()
8 k h8 s" a! i3 _, K - .storeUint(config.number, 32)% [% q* g# |' U8 z& n
- .storeAddress(config.address)! K% \' x! L# S% m% f
- .storeAddress(config.owner_address)9 v4 }6 Z6 t. _$ E
- .endCell();; |9 e0 X1 G4 q" R2 p
- }
* E: t2 h- G7 l3 B' u
5 c! P0 l5 q$ o8 B' I- // ...contact wrapper class
复制代码封装文件中还有一点需要更新-getData 方法,这样它也会返回 owner_address 我们还将创建新方法 getBalance,以测试存款后余额是否增加: 1 Y+ r% x$ v$ _: N, h1 j
- async getData(provider: ContractProvider) {0 j; l5 |; s q5 t
- const { stack } = await provider.get("get_contract_storage_data", []);& u- N8 Z* G1 d7 s2 ]# r* ^
- return {/ q2 t3 S7 g( y) P; P& I
- number: stack.readNumber(),. }) m6 _7 K, Y& U6 w4 Z( \0 y1 N
- recent_sender: stack.readAddress(), I8 C3 l1 t& }( [5 W( Y
- owner_address: stack.readAddress()," o" M+ r- j i6 |% m6 v
- };" W& G6 o: b w5 X, b0 h
- }
1 f' _6 X% _/ `# n6 e2 ~ -
/ r; h- F# @3 Q$ D, E4 p: z2 A' w - async getBalance(provider: ContractProvider) {
! a# i2 J0 h4 _ - const { stack } = await provider.get("balance", []);# J0 q4 f) j# b" ~7 y& a3 l
- return {: Z+ i/ P; {8 X& r
- number: stack.readNumber(),$ L) K- H) C7 m* ]# f8 r8 Z
- };
$ E; A8 e& }3 c! m9 X! j - }
复制代码- // ...library imports# ^, k1 P5 D, G7 u1 Y- x
5 e) c) {. B" h" [1 h: ?& q. C- describe("main.fc contract tests", () => {7 O/ G) ]- h* c' H" N0 Y
- it("should get the proper most recent sender address", async () => {) q8 E9 J: L; w! N7 A# a
- const blockchain = await Blockchain.create();4 w `% y2 M; v" Z; r: K; S! V3 J
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
9 V6 V+ z) @& q* h7 h" ^4 y
5 `. }9 `- [, c% b- const initAddress = await blockchain.treasury("initAddress");5 `/ b5 c$ m/ D, @, d* o
- const ownerAddress = await blockchain.treasury("ownerAddress");3 z: i {; |- |! {' t+ B# A4 c
-
0 F, M+ w r+ v - const myContract = blockchain.openContract(* W( G. C# u' t2 {# p
- await MainContract.createFromConfig(+ j3 w8 n0 d% w* U T
- {
& h) D( F" y3 E2 ]' ^ - number: 0,
% z# p; o2 l# p# k" A# r" Y - address: initAddress.address,
v( A; A: g) U2 U E V7 A% } - owner_address: ownerAddress.address, // now we create myContract from 3 parameters
- K; t$ a5 ^( i; E% ], b/ f - },
! c8 k g( @. r% v k+ W - codeCell
, I. _, o8 z( b# Q% y- a - )0 r8 A5 E. D# o0 {. k! U
- );1 B% ]& H1 x( h. }: q4 W0 T5 g
8 e" H; t) z% d: x- // ...rest of testing code
复制代码
: R3 l8 U, d {; G% J我们引入一个新的金库,它实际上将是合约的所有者,只有这个所有者以后才能提取资金。这些更新在 tests/main.spec.ts 中进行: - // ...library imports, t6 z% S8 N9 @
- 8 f6 B% B7 F0 K7 d% @: F9 K
- describe("main.fc contract tests", () => {
3 Z2 N0 k, f8 {6 T4 F0 k/ t - it("should get the proper most recent sender address", async () => {) c8 g! a$ F. x8 O J8 e2 `) h1 d
- const blockchain = await Blockchain.create();
9 n& o0 a7 |* O7 X5 F: m - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
3 S8 X3 q. ^5 r# A- l - 9 G9 ]& a* e7 ?+ f4 i7 [" z- s
- const initAddress = await blockchain.treasury("initAddress");
2 P; s- ~5 I" \1 A - const ownerAddress = await blockchain.treasury("ownerAddress");
) ]( Y/ j3 k2 M1 S+ u4 O -
* N7 f+ J+ }) L* G0 I1 ] - const myContract = blockchain.openContract(7 b1 N! m) z' A5 ?3 O; ?/ t% J
- await MainContract.createFromConfig(
7 R+ g k( e/ }1 Y! g - {: ?: `4 X) Z. g$ C& N
- number: 0,8 d5 |; l* X( ]& R9 @
- address: initAddress.address, y* i. J8 T8 [! {
- owner_address: ownerAddress.address, // now we create myContract from 3 parameters, Z) f) D0 Y2 C) U
- },
# W8 V" i/ _2 v& o* u - codeCell
5 s$ s' r0 O2 O( U; }1 E2 G7 U* _ - )
- j; @+ l$ L1 }; Q" `7 I - ); B0 e( c( d t/ s; Z
I) _% k5 u# ], u5 v- // ...rest of testing code
复制代码现在我们好了。 yarn test 成功通过我们之前编写的测试。
' e) d8 n9 s' m存款测试让我们在 main.spec.ts 文件中再添加几个测试。 由于我们必须在每次新测试前重新启动合约,因此我们还将把合约启动引入 Jest 的 beforeEach 函数: - // We need to additionally import SandboxContract and TreasuryContract0 Q% o+ Z& J6 W
- import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";0 q# t# Q }& K+ K3 X0 ]/ B0 b
- // ... other library imports
% t2 {4 Q5 p$ |6 L' G: z& { - describe("main.fc contract tests", () => {
& Z3 Q% z; G( t- K; ? - let blockchain: Blockchain;7 O9 B8 A' K5 F# ~7 ?; l7 |2 W2 j* o
- let myContract: SandboxContract<MainContract>;& t3 r- n% d! ~) l
- let initWallet: SandboxContract<TreasuryContract>;2 A, _/ P9 T5 l& P5 J
- let ownerWallet: SandboxContract<TreasuryContract>;
8 E2 j* {! x' y- D. B0 R - # i8 H; j- m% X- o
- beforeEach(async () => {
0 }7 l' B E6 n5 L - blockchain = await Blockchain.create();4 {9 u* S: {0 f- C/ e7 K+ g# X5 F, L0 G
- initWallet = await blockchain.treasury("initWallet");
# b& e( T5 e' f* N - ownerWallet = await blockchain.treasury("ownerWallet");) E3 a( c7 \3 H7 {
- 3 @9 h: h" x, a) _* m, u
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
6 e/ r1 z( f$ y4 M9 X/ e% g
6 V6 A" p# g) K7 @: @8 z8 g- myContract = blockchain.openContract(/ h/ V% Z: N8 e9 {3 Y
- await MainContract.createFromConfig(1 H e D0 A7 |
- {
" A3 U" `/ \8 k/ d7 e) Q. @ - number: 0, q/ o4 b: V* N( d7 @
- address: initWallet.address,) |1 ]) J2 b% E* x" z- V
- owner_address: ownerWallet.address,
+ |1 r/ r. g" l/ d4 Q/ v - },
! b3 p/ v& a U% h - codeCell
' z* T! E+ s/ \6 Q# R5 a; C - )2 }0 f6 @$ E2 P" ?/ q* t
- );
8 V5 ^: k4 J1 c8 d$ E @2 V - });7 }1 G" `& K% j7 ~9 a
- + i$ |1 R @/ Y7 O) | R
- it("should get the proper most recent sender address", async () => {" W ~) Y- V1 D. e# _9 k5 K
- const senderWallet = await blockchain.treasury("sender");6 R6 F; V& }5 s2 S' Y, }6 Z
6 a& M6 m& d" {' k- const sentMessageResult = await myContract.sendIncrement(
7 }) b% N3 w) _# ]) Z - senderWallet.getSender(),
. ~/ o/ v$ o' P0 L+ c9 _3 V" D - toNano("0.05"),
- o/ C0 a k$ N - 13 X2 d% v" ^; W& Z* @0 M
- );; Z. Q7 Q6 T1 c5 H* E
- + O, k2 Y# H" q0 f7 o' c
- expect(sentMessageResult.transactions).toHaveTransaction({
% b3 z* {' l" X7 F z) e& e - from: senderWallet.address,
8 r& u8 Y6 }7 `" ` - to: myContract.address,# k) K. j* q2 O% L3 b
- success: true,0 U6 \* o( M; ]: T0 g5 p
- });
+ w* P) r9 R1 N" E, x) [) f; R
" G& [3 U- p; c" v0 o" T. _2 i- const data = await myContract.getData();* c( `, w0 k2 }* }- b( u( r6 @
" D" \) {' h# N- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());8 x% |( ]9 s5 O/ p; M& w8 b
- expect(data.number).toEqual(1);
: v3 m& `; N- c) f$ a: A2 O" A. T - });( Y8 e o! _' U8 q! z3 o
- it("successfully deposits funds", () => {
: ^6 _/ y$ `+ |5 H - // test logic is coming' q h8 g" Q8 ~
- });
1 H! O" d/ w1 N4 ` - it("should return deposit funds as no command is sent", () => {+ c% V4 q; ]# V9 j1 B8 y1 v+ }
- // test logic is coming6 O: ~, Y" O% [0 Y
- });
% R* H- A& R- g/ i - it("successfully withdraws funds on behalf of owner", () => {
, s7 H g; M* W - // test logic is coming
$ f7 e4 @: x k5 ~# G! {' { - });
) L, i1 c+ o" A2 a1 d; f P( L - it("fails to withdraw funds on behalf of non-owner", () => {2 p/ N( x$ w2 e/ X* B
- // test logic is coming; C/ R$ _7 V# U# |1 _( u3 o
- });
' M/ K7 T$ `. Y- K2 z3 K$ p* } Q - it("fails to withdraw funds because lack of balance", () => {
J6 I9 V% E7 w$ J - // test logic is coming9 p( I; c9 K% F) W
- });
6 B9 l% `! T# q2 M5 ?- S) A( D - });
复制代码我们只是将第一个测试的部分行移动到了 beforeEach 中,其余的行应留在第一个测试中 it
让我们为存款写一个测试。我们将发送一条 op == 2 的信息,然后检查余额。像往常一样,首先创建一个封装方法,我们将其称为 sendDeposit: - async sendDeposit(provider: ContractProvider, sender: Sender, value: bigint) {
: Y [! D7 q7 F+ p' J9 r - const msg_body = beginCell()
) e; F& \8 B+ R0 d. X6 I9 _3 _$ z9 j - .storeUint(2, 32) // OP code6 _$ p. C X2 {9 ?% s% Y: y) y
- .endCell();) P. M' j0 F4 \6 | o
; G# g. y. w* ]2 x7 x+ A5 ~- await provider.internal(sender, {
" r7 r) N: V3 L2 L# s( ~6 f - value,
8 i: n# R, f% t5 R3 W' J# m8 ? - sendMode: SendMode.PAY_GAS_SEPARATELY,3 e. `1 R' j% D- v1 q7 T' E
- body: msg_body,
8 n0 Z. m4 O2 _! b7 E& H" T - });# u8 O; ?1 d- u: k1 g7 k0 ?
- }
复制代码我们将不详细介绍这种方法,因为它非常简单--我们只需在信息正文中传递一个操作码。 下面是我们如何使用该方法运行存款测试。 - it("successfully deposits funds", async () => {. V% H9 w I" D) J2 y" T
- const senderWallet = await blockchain.treasury("sender");3 U/ X+ q( H( d9 j& @7 ~
- 7 B0 \- F d: x% t8 I
- const depositMessageResult = await myContract.sendDeposit( t9 d8 p8 @2 ~* ~4 R2 j! s
- senderWallet.getSender(),
1 ?' L5 }. D+ [+ G - toNano("5"). T `& u. {) T8 P+ Q
- );
' @+ U1 W; b. `# h$ D5 x% p - * x. N9 e; b* H+ p1 n
- expect(depositMessageResult.transactions).toHaveTransaction({
7 L; @% N( f- t& o0 c' K( t - from: senderWallet.address,; G% ^7 L7 w9 H! B% [
- to: myContract.address,8 m. |# p, D V7 T8 I1 }0 A
- success: true,8 X; B- p& E/ H' t& a d
- });5 M- {9 L7 @" y! R, y, g
' ^# K/ U' G" f# F7 t- R6 c% O- const balanceRequest = await myContract.getBalance();
' j/ D s- M/ r! g5 l
) i1 }5 {3 I R8 C& `9 {, u- expect(balanceRequest.number).toBeGreaterThan(toNano("4.99"));$ u7 J: G4 p) f( d$ Z! }- s" c% S
- });
复制代码请注意我们是如何检查合约余额是否大于 4.99 TON的,因为我们知道手续费会损失一些资金。 我们再写一个存款测试,但这次会出现误报。我们将在没有存款操作码的情况下发送资金,并期待返回交易,因为该操作码未知。 和以往一样,我们还有一个包装器: - async sendNoCodeDeposit(: e# l+ O& e( z* A% K+ j
- provider: ContractProvider,
" n) S* H, F! Y% c ~0 m - sender: Sender,
6 r, \; Y g4 E( U - value: bigint/ Z- I8 d% _$ o" f& g0 @/ U
- ) {
4 v/ `- L% ~+ R& c' k - const msg_body = beginCell().endCell(); N& C7 p6 ` m: g3 D X8 Q8 x5 L
- 4 r0 L" l9 p7 k( C- m+ e$ F
- await provider.internal(sender, {- f0 Q+ s$ [! L* |$ O+ G
- value,
3 f4 l/ P6 i8 E/ M0 z. Q - sendMode: SendMode.PAY_GAS_SEPARATELY,# Q9 y0 x) K( e) p* b! z, `
- body: msg_body,/ Q3 W n& p! K/ P ]9 B5 J
- });
- ^, [( U! x' e0 F - }
复制代码下面是测试代码:
" \1 e; o/ G$ u$ i& V4 N/ h6 ^, N- it("should return funds as no command is sent", async () => {
3 \$ s, ^: H3 ^( x. e - const senderWallet = await blockchain.treasury("sender");
) p r- E: P7 S. ]
! Y7 w; E I1 ]/ S- const depositMessageResult = await myContract.sendNoCodeDeposit(8 i5 ^5 F5 L) `7 K
- senderWallet.getSender(), _( A- G e$ h. E+ K4 q3 \& i% [
- toNano("5")
% h/ Q. c1 }4 l6 c$ A - );& }* J- t8 O! M* c/ ~
- 9 l* C- v. ?& V3 T' H
- expect(depositMessageResult.transactions).toHaveTransaction({
; m5 t: Z { e1 G) B/ E( W - from: myContract.address,0 Z g r9 V$ O! P9 Y6 S& @+ _
- to: senderWallet.address,
2 h+ }* y# |6 s) ?. f - success: true,
3 p: N8 F5 s/ r% o2 | ` - });/ e: J3 v) R& ]: [7 k, j5 y
0 A: O% G) r$ d: |/ |: o- const balanceRequest = await myContract.getBalance();
" _" R& U& o$ L
( g& I! ^2 C6 O& q# u0 t: C- expect(balanceRequest.number).toBe(0);5 @/ @4 t s0 K8 ? B
- });
复制代码 提款测试让我们先创建一个用于提款的新包装器: - async sendWithdrawalRequest(
- p/ J4 K6 _/ J2 U9 r) v( t( [3 d - provider: ContractProvider,
5 p9 t6 m( J _& H% [# u! m, ?$ p - sender: Sender,2 @- D" Q" {4 }6 V- @: Y5 A8 W Y. S
- value: bigint,
5 P9 L0 s* d$ p - amount: bigint x; L2 C5 H- h. g, i
- ) {5 s9 Y+ e7 s7 B4 _8 E! D
- const msg_body = beginCell()! I" a3 v4 d$ E8 {
- .storeUint(3, 32) // OP code
; o1 I8 x4 g. B: a - .storeCoins(amount) U" B8 F1 C; h' T4 { H7 r3 G8 {
- .endCell();
- G$ m/ }/ {, _8 }# Y) N: \ - # i9 ^5 L( b3 c" s, O% @
- await provider.internal(sender, {
7 j6 l8 f6 L. P" A0 k - value,
$ m, n( P% V( I# h0 _ - sendMode: SendMode.PAY_GAS_SEPARATELY,
1 N4 v8 U6 @$ p - body: msg_body,
5 f$ C3 g% I1 D' h v - });
7 ^4 i8 S5 f! w( @# p P - }
复制代码我们输入适当的 msg_body 和所需的提款 amount 。 我们将进行 3 项不同的测试。让我们一起来看看: - it("successfully withdraws funds on behalf of owner", async () => { B0 H I: v3 e" z
- const senderWallet = await blockchain.treasury("sender");
* x( @8 \) J( h b - ) R3 J z8 V7 U7 S( h% \
- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));1 _, D/ r) ?* ~
- 8 X" }9 q' q% T8 w
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
' L: a& ]) U5 \ v - ownerWallet.getSender(),
( P% [+ H6 L, {9 t- A3 k4 _/ J4 s' u - toNano("0.05"),/ Q5 a# D, u# D6 C4 I% [( ?
- toNano("1")5 H4 h1 j8 t( E% Z+ ?* U3 J, f( k: v
- );1 k" o: s8 Q+ c* K& O/ r
5 l% N' |+ u1 T- expect(withdrawalRequestResult.transactions).toHaveTransaction({
+ B6 t1 e3 @% h3 Y - from: myContract.address,
4 R; }9 e5 J6 N/ T - to: ownerWallet.address,
& A* d. {" T4 {4 J* |5 K8 L - success: true,
. {% r1 ?1 m" [: w9 @ - value: toNano(1),
H: T* Z4 Q/ W* _ - });
3 [/ A. S" P; c) \; X1 J& a - });
复制代码 要成功提款,我们首先要正确存款。然后我们调用 sendWithdrawalRequest 指定我们要提取 1 Ton。请注意,0.05 Ton只是为了支付费用而指定的信息值。% |4 C$ A4 g% |, H
- it("fails to withdraw funds on behalf of not-owner", async () => {
$ C4 T9 b% z, I+ }8 H0 _+ l; ?, k - const senderWallet = await blockchain.treasury("sender");' M+ Q( g% ?$ ~+ J
- D# X; ?2 B/ e/ a' m7 q# R- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));
9 S1 i' l% ]8 ~+ v
) V( O% {1 P1 ?4 K. ? ?; m0 C- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
7 C' ~# m) @5 s% T( b5 U' L - senderWallet.getSender(),# h8 O* X; G1 v5 i
- toNano("0.5"),
& E6 |# a& K% O9 Q, I+ `$ b - toNano("1")+ `7 j2 e$ t/ H. U1 a
- );1 M2 }: {. _, M( B
/ T) ^5 m) C. _! G+ J! C- expect(withdrawalRequestResult.transactions).toHaveTransaction({& {+ Q4 R# G# f# M) K K* t
- from: senderWallet.address,! `7 z# K- B f: U$ r
- to: myContract.address,
$ n' S) M8 D7 S% `+ R" D - success: false,
) y* _! ^4 }: V# m! ?8 t9 D - exitCode: 103,
- |' b! X. A0 ? - });
6 b- n* w, S8 R - });
复制代码在本次测试中,我们代表发送方钱包而不是所有者钱包发送提款请求(两者的区别在于初始化时我们提供的助记词). 还请注意,我们是如何通过 success: false 和具体的失败原因 exitCode: 103. 为了确保我们做得正确,请看一下我们的合约代码,我们有这样一行代码: - throw_unless(103, equal_slice_bits(sender_address, owner_address));. ?# N# E+ Z7 N5 l7 r
复制代码我们进行最后一项测试--由于没有余额,提款失败: - it("fails to withdraw funds because lack of balance", async () => {
" U ?% `7 z1 h0 Z4 _ z- O - const withdrawalRequestResult = await myContract.sendWithdrawalRequest(& H. B) O7 B' [. L! g% t) s: v3 B
- ownerWallet.getSender(),
d" \6 R* X9 V. q - toNano("0.5"),
?4 v* p* k# }6 x - toNano("1")& |; X9 z% G( X) `. T7 H
- );9 Z4 n# M9 o" d5 l! y
* u u& S1 |8 m# r6 _/ m- expect(withdrawalRequestResult.transactions).toHaveTransaction({
5 S- M# |7 C" R9 u2 ^ - from: ownerWallet.address,
$ q/ T0 |" K4 H/ z - to: myContract.address,5 @' a- \- A |) \" A' T
- success: false,
: e, j1 i- P2 R+ m - exitCode: 104,# e" b! ]. ^$ E7 K
- });% e+ T1 z5 {0 \* c3 @" d3 c
- });
复制代码- throw_unless(104, balance >= withdraw_amount);( F; o% [& \8 n, m: \* `
复制代码就是这样 几章之后,我们将学习如何构建网络客户端,以便与合约进行交互。 & [& F7 ]& E& S
* U! q( }8 G9 a/ l$ X4 m2 j( n |