在本课中,我们将编写一些有趣的测试。 首先,我们要更新之前编写的合约的初始化过程,因为我们引入了一个新变量,它应该存储在 c4 存储器中。 9 - Cell下溢。从片断基元读取数据时,试图读取的位或引用数超过总数。 更新我们的合约包装器首先,让我们更新我们的合约包装器,以便它能意识到 c4 存储中的第三个参数。也就是说,我们将更新 MainContractConfig 和 mainContractConfigToCell 函数 - // ...library imports
3 K& [1 V: t4 ?1 _
' P# E U. G5 s2 k/ b/ M8 T- export type MainContractConfig = {
5 _6 ]2 [: M* P* n - number: number;/ k) D: t* `' f# Z
- address: Address;
- m# L2 E3 Q) d5 O5 o* _' s - owner_address: Address;
. p1 N5 e2 x* ?! u+ o8 F - };/ m, K. f4 |$ _& c7 C: ]
- % G( `7 q* W' _; l) Q# T
- export function mainContractConfigToCell(config: MainContractConfig): Cell {
2 I$ n3 M0 K+ o% Y0 {! s - return beginCell()" ?2 Y7 N- F/ j
- .storeUint(config.number, 32)3 D* [" e: o8 V0 Y7 s
- .storeAddress(config.address)8 m( {2 @$ A& ]+ h) F
- .storeAddress(config.owner_address)( d1 @# a# U4 ^7 m
- .endCell();
- [9 e0 M0 R0 a' L9 h" }. @6 ~ - }
7 E# \3 C" C4 S
6 i) K$ Z/ |/ `4 K9 F: A, g, _- // ...contact wrapper class
复制代码封装文件中还有一点需要更新-getData 方法,这样它也会返回 owner_address 我们还将创建新方法 getBalance,以测试存款后余额是否增加: : g. }6 Z# ^' J5 e9 G0 g G" K+ C1 e
- async getData(provider: ContractProvider) {
' t. s4 ?+ l y5 ^ - const { stack } = await provider.get("get_contract_storage_data", []);
1 y k. @+ f' k9 J3 |9 T - return {
( f5 l. H4 Q! W: F$ {2 q' C - number: stack.readNumber(),( a( Z" x2 u+ @6 A
- recent_sender: stack.readAddress(),* L i0 `2 }; _( I6 R7 {
- owner_address: stack.readAddress(),
' z. O1 |7 t9 q& i+ ? - };: f" f* |1 k7 V1 G7 ]
- }8 Z% G/ B- ^! [
- 4 g* c ^9 b1 Y3 L2 [' n
- async getBalance(provider: ContractProvider) {
* J! v2 D* r0 n - const { stack } = await provider.get("balance", []);2 I, A. @3 I- Y
- return {
7 g& r: C' p0 H8 w - number: stack.readNumber(),, }: w0 x* y6 {
- };# X" U1 n# h4 M, I& _2 z' l1 j
- }
复制代码- // ...library imports% y+ r6 m! z+ o8 L! C& H, z
c( @% |6 v0 m/ x* v6 \$ T- describe("main.fc contract tests", () => {) c& ]/ e X1 g
- it("should get the proper most recent sender address", async () => {
* y$ g7 G0 \5 c2 l% B - const blockchain = await Blockchain.create();
' |, _8 L" N. {# V/ { - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];, n' X( V# L9 l# J
8 p2 D6 D% B) Z- const initAddress = await blockchain.treasury("initAddress");
r0 h% h3 H" y$ k/ B3 V: w - const ownerAddress = await blockchain.treasury("ownerAddress");/ o) u0 t$ C6 z# Q
- : B# n: ^( `+ @4 _) G0 v* p0 o
- const myContract = blockchain.openContract(! C: q( [! K% e+ i" x) w
- await MainContract.createFromConfig(
" D# N2 n2 L* g - {
; D `! b3 j# ?0 V8 H - number: 0,3 t$ {5 p8 U( M# P" B& r
- address: initAddress.address,
( V( y4 `; d$ Q4 t( ` - owner_address: ownerAddress.address, // now we create myContract from 3 parameters
& ?" I" z5 d( S% l1 N% o - },4 V- [7 J2 b8 H' T# X# j. W1 E6 c# r
- codeCell
" e% \8 G# d0 ]: V - ). o1 ]4 ~1 @- X8 H0 |
- );, P' _% M. t+ A7 |. w
2 G; J; a6 C& N& y6 S$ ~9 b6 U- // ...rest of testing code
复制代码
- u3 a+ _5 w i4 W: e我们引入一个新的金库,它实际上将是合约的所有者,只有这个所有者以后才能提取资金。这些更新在 tests/main.spec.ts 中进行: - // ...library imports
3 t( Q/ T1 u- T! C - / T. {. L2 ~, Z0 R; ^! T/ b
- describe("main.fc contract tests", () => {! Y g" K" `' [! K
- it("should get the proper most recent sender address", async () => {
; F+ @5 P- @9 x) w - const blockchain = await Blockchain.create();6 l5 l' {: e) @- |' ~
- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];8 Y9 k, `+ t* `" W9 e! U5 m
: F7 |, q1 @0 I8 ?- const initAddress = await blockchain.treasury("initAddress");
+ C6 b% H I8 A3 L - const ownerAddress = await blockchain.treasury("ownerAddress");
- |4 d. P. m+ q6 b) [ - ' F) z" D9 N2 Q) _1 a
- const myContract = blockchain.openContract(
( d& P7 `8 H8 o7 c) ~2 c% C' h - await MainContract.createFromConfig(! Z" U' b( @9 i T6 m2 s% H
- {
8 ?0 d' C" ^2 c% @/ Q - number: 0,
7 O8 A. z0 v) }* j7 q. Z0 u - address: initAddress.address,8 r b% H5 V" ~6 r7 `6 B
- owner_address: ownerAddress.address, // now we create myContract from 3 parameters" l* C+ o, S2 R) U
- },7 |& h. Y" b; i1 j$ l* O
- codeCell
. y8 U6 o- B) w z: l - )
8 b* J7 q) w9 q9 t& s; D7 } - );% u" X% |" k2 c' T" R
- $ K% A- q/ u" @, N5 a1 t6 @( i
- // ...rest of testing code
复制代码现在我们好了。 yarn test 成功通过我们之前编写的测试。 6 ?8 P4 N+ D A3 C- D& p- {5 a4 g
存款测试让我们在 main.spec.ts 文件中再添加几个测试。 由于我们必须在每次新测试前重新启动合约,因此我们还将把合约启动引入 Jest 的 beforeEach 函数: - // We need to additionally import SandboxContract and TreasuryContract- ]0 G" O+ }) W7 q1 Z3 f
- import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";5 \/ B* v% i! q5 W
- // ... other library imports$ \* {; H- e% i$ T
- describe("main.fc contract tests", () => {: `) I U' ?0 H& B8 [% g% T3 X4 T
- let blockchain: Blockchain;
* K% m7 j! p. ~7 D8 P( j! `9 B - let myContract: SandboxContract<MainContract>;
0 M4 D9 ]& A/ e% L v1 v: \* l - let initWallet: SandboxContract<TreasuryContract>;
* Y- h4 E8 I7 J9 A( J5 h - let ownerWallet: SandboxContract<TreasuryContract>;: \& H0 M8 v4 t) {
" H5 [' Z! k- `- beforeEach(async () => {
$ |6 C: U/ {0 i# E - blockchain = await Blockchain.create();% A; _8 {2 D' M" x
- initWallet = await blockchain.treasury("initWallet");
' _- R- M4 t4 s, A/ i - ownerWallet = await blockchain.treasury("ownerWallet");
! h9 V: ~1 F8 \4 d# D
3 q& M9 K+ B; |0 C9 O: Q- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];4 `. I/ F% k+ e; u$ x: a
- u3 \9 Q- t- X) U3 e5 z( d- myContract = blockchain.openContract($ o( i* `# s1 O _
- await MainContract.createFromConfig(
$ s& J0 G) _8 K* T& P - {
7 [ W3 m6 R+ G- S, Z - number: 0,
7 e! K" Y7 L3 d1 F6 D1 ^ - address: initWallet.address,
7 A- }& o7 D& A - owner_address: ownerWallet.address,9 T* Q; _3 V9 `& E3 v; y+ B. |- x
- },* B% t: d4 K8 B' s0 a5 H
- codeCell
8 D* U: h1 ~1 V* J: H! G - )
# [% g5 x2 ]% D0 D) W5 `" O - );4 G$ {5 E4 W2 a) c+ t
- });
' D- Q, r, Z2 |: o( G4 u; W3 E
9 V7 M5 @1 X, V# k& u- it("should get the proper most recent sender address", async () => {
, L/ M5 {, ]3 P4 e2 Y, k - const senderWallet = await blockchain.treasury("sender");
4 O. K L, T( {) A2 }: v9 m9 L - c+ o0 y$ i" P' | e
- const sentMessageResult = await myContract.sendIncrement(
( b( d# V; K1 K8 b; T( Q - senderWallet.getSender(),
9 y& ^. X9 y& K; {7 Y8 `# t - toNano("0.05"),
: H% B$ n0 K" T. X) i8 Z - 1! Y9 e& o# p1 D' [' A: l! Q5 P1 ]
- );8 E0 { d4 [8 g" b. Y
- * P p% M* y5 ~2 O$ V# o' t6 l/ I
- expect(sentMessageResult.transactions).toHaveTransaction({/ \- k( F" P {' J. O6 c: H2 L& `
- from: senderWallet.address,7 r9 x& f m- U" F; t/ |
- to: myContract.address,
, _/ g' y" L, z( h - success: true,
6 ^# | S, y8 d+ @5 Q - });. ]5 S, J7 D( [
9 `/ k( C4 d. N, Q- const data = await myContract.getData();
) Z, C5 q* e6 H( I+ ]
# g; n9 s& Z) ~6 r- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());+ O0 E. J3 Q8 R2 X' {5 `4 |
- expect(data.number).toEqual(1); r+ J, k. H n6 A$ F$ L% y5 `; X
- });
0 Z+ C% T/ W- F' @ C. `: V# O' T - it("successfully deposits funds", () => {( Y; Q. I$ g' k% m$ C
- // test logic is coming
, L! w+ A9 r0 ~4 K - });2 b1 Y9 T9 S; a; p# r& d9 @; v
- it("should return deposit funds as no command is sent", () => {" b( G) S9 b4 U" {3 ?
- // test logic is coming! R7 _" r% o+ c/ J" x8 F9 t) [
- });
4 o3 \% n8 ]- g. Z - it("successfully withdraws funds on behalf of owner", () => { z; H, x/ d/ a: B* |
- // test logic is coming
5 ~2 T" ^% E! I# p - });% e7 D1 z# n8 \* Q0 \
- it("fails to withdraw funds on behalf of non-owner", () => {
$ Z3 M0 Q2 Q( b5 P% R4 t - // test logic is coming
8 g/ H/ q& P& e* m3 p2 G: d - });
. w! J! f& z: b: K5 U! ?7 B4 W* R. W - it("fails to withdraw funds because lack of balance", () => {: Q8 y N) F& Q/ I2 t
- // test logic is coming& M3 l9 p6 O' g! k) p$ B& r1 C
- });4 P9 w( |/ s) O; l& t8 W
- });
复制代码我们只是将第一个测试的部分行移动到了 beforeEach 中,其余的行应留在第一个测试中 it
让我们为存款写一个测试。我们将发送一条 op == 2 的信息,然后检查余额。像往常一样,首先创建一个封装方法,我们将其称为 sendDeposit: - async sendDeposit(provider: ContractProvider, sender: Sender, value: bigint) {* t u6 i# u4 i
- const msg_body = beginCell(); C+ U6 P; o/ v" L, c6 v' F
- .storeUint(2, 32) // OP code$ [& v6 U& }& `5 p/ E
- .endCell();
7 @( Q4 Q8 j$ h L* a# _2 n - # S. Z+ S! d# j) V
- await provider.internal(sender, {
$ j$ n- Z" b D- T - value,
) g6 I+ c& ?! I# Y2 g8 J; y4 w e - sendMode: SendMode.PAY_GAS_SEPARATELY,% i+ w% C( v y# D
- body: msg_body,
9 d4 M. s2 W/ b - });6 K3 z K, {" y2 g
- }
复制代码我们将不详细介绍这种方法,因为它非常简单--我们只需在信息正文中传递一个操作码。 下面是我们如何使用该方法运行存款测试。 - it("successfully deposits funds", async () => {8 y0 P3 m2 z; b/ X$ `
- const senderWallet = await blockchain.treasury("sender");( d# P+ N& J& x; `) b/ a/ X
- # z7 j; `* o( [, o
- const depositMessageResult = await myContract.sendDeposit(
5 y7 |- B0 {3 M* n& [: U; c" l* e0 W - senderWallet.getSender(),$ C! t* b1 E9 e4 A. i) ~% _
- toNano("5")
% c* w/ @& L+ z- Q! x - );
$ y q5 y `/ O+ I: _" S+ u- ~ - * w; O# }) A, c, Q: ]0 L
- expect(depositMessageResult.transactions).toHaveTransaction({( W, q5 U* |% `. m* k4 |; w* N
- from: senderWallet.address,
. p3 k$ _0 s+ |( o' ~4 Q - to: myContract.address,: S& V1 T" m! S9 f# X6 q, U7 j" G- K
- success: true,
2 n- s/ Z3 H* S# R - });
, m7 O4 {& I% v* W" M1 ~0 _7 V9 k - ( f, g& I4 B* j/ E
- const balanceRequest = await myContract.getBalance();& T# ?) V) s* S5 O( z* |8 w/ H* V
- $ L4 |2 G/ g% l% f7 [+ X
- expect(balanceRequest.number).toBeGreaterThan(toNano("4.99"));1 A/ {) O y$ F6 f! H4 }) n
- });
复制代码请注意我们是如何检查合约余额是否大于 4.99 TON的,因为我们知道手续费会损失一些资金。 我们再写一个存款测试,但这次会出现误报。我们将在没有存款操作码的情况下发送资金,并期待返回交易,因为该操作码未知。 和以往一样,我们还有一个包装器: - async sendNoCodeDeposit(
9 U8 _* Z e8 N - provider: ContractProvider,
, [, n# G, j' g% f - sender: Sender,
: j& N) b0 }; F0 f - value: bigint! n0 y7 o4 ^5 x, g% ~% J$ l8 e6 n
- ) {
* a+ h' `( a R ] - const msg_body = beginCell().endCell();
2 } j" O( M* Y. W - ( A. b0 t* \, _3 C
- await provider.internal(sender, {
. A9 O4 o+ Z. ~0 b0 t. { - value,
) v$ ~+ R" w. Y) |& I3 J w - sendMode: SendMode.PAY_GAS_SEPARATELY,$ ]/ _/ G% {" m# b, _
- body: msg_body,
7 o7 H4 x' k3 e, }8 D3 L - });' S& ?% B# r }) e
- }
复制代码下面是测试代码:
7 F: }/ ]$ C9 p! z- it("should return funds as no command is sent", async () => {
) ~( j* u ~* g' c - const senderWallet = await blockchain.treasury("sender");
; Q) _5 s0 c& @) P' N - & a3 T3 E, P( X$ d' j$ v& j; V
- const depositMessageResult = await myContract.sendNoCodeDeposit(6 s% W% \9 D3 K( [) k! U! T" C
- senderWallet.getSender(),2 a9 E! p& \- Z, Q1 J! a* o3 D
- toNano("5")
# C' J( I7 X- M1 M2 ]$ P k8 | - );
. {2 N% _8 Y4 V; C, v6 X; d% Z - 8 k7 k2 a4 A- Q# {, J) G9 @
- expect(depositMessageResult.transactions).toHaveTransaction({) x( ~3 C" d" H
- from: myContract.address,
# `% r0 j8 r: ?3 n( j5 Q: D - to: senderWallet.address,- e. h+ f0 b. q% _
- success: true,* w2 l1 ~7 H, ]( P0 F9 |3 n
- });
( i+ ?0 S8 h: K( @1 _% s( m - : y9 F3 l: L: h6 L
- const balanceRequest = await myContract.getBalance();
- V4 `$ J8 _9 _( @( N N/ f - l! e4 C4 T f9 ]5 ^* v" P) P7 _ F
- expect(balanceRequest.number).toBe(0);) R) k' d6 G; u/ W$ J" o" B
- });
复制代码 提款测试让我们先创建一个用于提款的新包装器: - async sendWithdrawalRequest(7 \; @( I; I! J7 N! p' K
- provider: ContractProvider,
9 G& H, \8 D' \ P Y - sender: Sender,
' E6 m/ R9 a3 n) g) B0 B - value: bigint,
% U; w+ p) P( i' @! c+ @ - amount: bigint
/ L% x( e% c5 z8 i, J- `, } - ) {& f( ?) C, V7 t9 g
- const msg_body = beginCell()
: T8 n; m6 }8 E; Q W - .storeUint(3, 32) // OP code9 ^; N) ?% v3 x" {1 M
- .storeCoins(amount)4 b/ }* V( o' \% F$ \6 Z. T
- .endCell();
8 r" C+ ]# w# B7 A9 @ - # A* ?' |1 H) B6 P ^# h
- await provider.internal(sender, {
6 W; B; v* v4 Q7 m( A0 `3 Z0 A - value,
( ^, [& `& w- D: k( ], d( w9 |* Q( Z4 h - sendMode: SendMode.PAY_GAS_SEPARATELY,
- z" M. }' Y% Y7 | - body: msg_body,
9 o9 t) ]3 G% N" a5 @$ S d! i - });
" T% K9 @: ?" Q - }
复制代码我们输入适当的 msg_body 和所需的提款 amount 。 我们将进行 3 项不同的测试。让我们一起来看看: - it("successfully withdraws funds on behalf of owner", async () => {! x u0 s8 b0 L3 _7 p* p
- const senderWallet = await blockchain.treasury("sender");3 j. ^9 B' F! ]1 X6 U* a% f$ `
- & x4 Y) @/ T6 s, q" Q" W6 y( K
- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));; i# g+ Q9 A: l( k K9 V) Y
- 5 H3 {. `1 ^5 w9 Q1 G( G
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
5 y2 D! N: A, |9 {8 A& P$ G4 f - ownerWallet.getSender(),) O' B- a/ H2 k3 ~# i* k* @
- toNano("0.05"),8 z( a8 k. V" g/ h) K
- toNano("1")
# t7 z4 M) A2 x; T9 l1 W; [ - );
. n! P9 Z/ Y! ?& q; T8 y9 k1 _ - ! \! {3 K; H7 J' J! Z
- expect(withdrawalRequestResult.transactions).toHaveTransaction({! U6 }8 |' @) e4 p* g7 ~% \
- from: myContract.address,
! e; _5 `' f6 Z6 p6 z - to: ownerWallet.address,, O- D X; `5 N+ X
- success: true,+ G, [1 a+ u- D( o+ e6 u
- value: toNano(1),
' H/ ~0 {; h Z Y - });
+ W4 J& b% Q& Y: W& F0 \9 a - });
复制代码 要成功提款,我们首先要正确存款。然后我们调用 sendWithdrawalRequest 指定我们要提取 1 Ton。请注意,0.05 Ton只是为了支付费用而指定的信息值。/ J% R9 Z1 E% K9 s' I5 ~
- it("fails to withdraw funds on behalf of not-owner", async () => {
* x8 K4 Y5 c& q* m- K5 V) D& f+ S - const senderWallet = await blockchain.treasury("sender"); W y) {* t, f# ]" c
* I# I7 D% T: h6 ~- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));
. N# `! c- F# ]6 M2 Q
- e& @0 B8 {8 Q' T/ J- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(# ]8 p/ h n9 ^( s
- senderWallet.getSender(),
9 `7 q/ s* z; M0 u9 }* z) G: L - toNano("0.5"),
7 o5 K0 {! E+ l! O3 d7 Q - toNano("1")( ^; V t1 H: F
- );& @$ I8 Y! ?2 U
* F+ u0 G9 F Z& F, `. }- expect(withdrawalRequestResult.transactions).toHaveTransaction({
' e3 }; C8 V3 I$ C7 R - from: senderWallet.address,
5 r) Y8 R* s$ a - to: myContract.address,; e. E* U" s7 ]% C
- success: false,
, T" B9 w; { p z! [1 G. T - exitCode: 103,8 | W& [+ x4 j* O$ f9 f- A8 g
- });
, r5 d6 j7 i# c) k - });
复制代码在本次测试中,我们代表发送方钱包而不是所有者钱包发送提款请求(两者的区别在于初始化时我们提供的助记词). 还请注意,我们是如何通过 success: false 和具体的失败原因 exitCode: 103. 为了确保我们做得正确,请看一下我们的合约代码,我们有这样一行代码: - throw_unless(103, equal_slice_bits(sender_address, owner_address));
- W9 s( y3 S1 |$ |! u
复制代码我们进行最后一项测试--由于没有余额,提款失败: - it("fails to withdraw funds because lack of balance", async () => {% n( e: A( a3 d1 \$ [: c
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
+ n- c$ }6 u" f8 M; t - ownerWallet.getSender(),$ s! i+ _8 x" q
- toNano("0.5"),
# a; r" l* @) n+ d+ f5 R, i - toNano("1")
) F+ P8 V, m( z1 ] - );
. s2 d( K$ H+ t2 s2 T% e - 3 M1 g# ?& I8 R$ c
- expect(withdrawalRequestResult.transactions).toHaveTransaction({
9 H0 @9 A0 i3 E! J1 n( u# B6 v3 R - from: ownerWallet.address,
- g# B. \& A' F+ J - to: myContract.address,; Q7 o2 ?2 _3 h( {6 Y
- success: false,
0 \! G3 e8 T. y# a/ J# j8 w: p% M - exitCode: 104,* r* P A2 B- ]( \* M
- });. i9 ], V" A8 {) N5 f' H. A' p
- });
复制代码- throw_unless(104, balance >= withdraw_amount);
. H, t+ k- ]( H: b5 E
复制代码就是这样 几章之后,我们将学习如何构建网络客户端,以便与合约进行交互。
* c o, {: d2 }' t) M; Z# i2 s" t
- h- _( T7 j1 y |