在本课中,我们将编写一些有趣的测试。 首先,我们要更新之前编写的合约的初始化过程,因为我们引入了一个新变量,它应该存储在 c4 存储器中。 9 - Cell下溢。从片断基元读取数据时,试图读取的位或引用数超过总数。 更新我们的合约包装器首先,让我们更新我们的合约包装器,以便它能意识到 c4 存储中的第三个参数。也就是说,我们将更新 MainContractConfig 和 mainContractConfigToCell 函数 - // ...library imports/ o7 z8 F. k6 [8 p& q5 O1 K& x5 B
0 C4 N1 I5 Q+ E! c, h8 q- export type MainContractConfig = {
; A" w( w$ P2 d# s! x2 O, A0 s3 k - number: number;) z6 e7 c( H/ o9 _! h0 U
- address: Address;6 }- {3 e ]' k" t) o% l, K
- owner_address: Address;
* S6 ~$ f5 C" W2 ^$ `+ J( {5 ? - };( o m; B0 a7 k
- P& [; e6 u0 h! D a, b
- export function mainContractConfigToCell(config: MainContractConfig): Cell {
' b# A. H5 Z( R: j, h - return beginCell()4 q& }; O6 H, d
- .storeUint(config.number, 32)
% w7 u8 W$ t m - .storeAddress(config.address)# p( y' q. d j% L0 d
- .storeAddress(config.owner_address)
: K, X: P# F0 K p: A. M - .endCell();) B5 O" g: }$ n- H9 m2 B: K
- }
( @+ ?1 v% Q, C2 o
5 c9 P p- J* |" h+ h I$ q- // ...contact wrapper class
复制代码封装文件中还有一点需要更新-getData 方法,这样它也会返回 owner_address 我们还将创建新方法 getBalance,以测试存款后余额是否增加:
6 h& u. }& W* S+ H5 M- async getData(provider: ContractProvider) {
4 M4 N' x3 ?8 s/ s y2 l9 Q' z2 s - const { stack } = await provider.get("get_contract_storage_data", []);
, M9 \6 U9 Y+ j* b" Q; r2 m+ W" f! D K - return {
+ B. C" X3 S" a* D4 J1 d - number: stack.readNumber(), _9 Q9 q7 i3 K
- recent_sender: stack.readAddress(),0 I# F: @! _: M3 B, u1 P- T. ]: a
- owner_address: stack.readAddress(),
+ M) y2 m( y/ i/ j; d - };
; O' ]6 V' J- `4 Y% j - }2 J" A% _' ~/ l8 |$ h: T& F
-
9 @1 w! h1 M# M# H% `9 p - async getBalance(provider: ContractProvider) {) B7 n1 b; J) k' B1 R, \8 E/ f( d
- const { stack } = await provider.get("balance", []);
% ?4 I( w, J; p9 E$ ^9 W6 u# { - return {# Z3 L2 G; N& ?% H. ]4 _/ ~
- number: stack.readNumber(),
& W7 h: w. X. g, {' I- C u - };
7 X1 B, L* u0 D - }
复制代码- // ...library imports, m. f. O6 ~( S! S* k! c
- * E: g1 ^/ m( T8 N; L7 L' |- m
- describe("main.fc contract tests", () => {# H: x! T; W+ X2 e
- it("should get the proper most recent sender address", async () => {
6 _* y7 H0 R% n2 X - const blockchain = await Blockchain.create();
$ L* F1 x |. o0 } - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
2 B! }- p) v1 k( z - $ n8 p, _% C( K. g: Z% F
- const initAddress = await blockchain.treasury("initAddress");" Q2 w) Q! P' z5 [, d
- const ownerAddress = await blockchain.treasury("ownerAddress");
1 D1 Q$ x9 n4 L4 w& ~5 b# c& ?: i7 k -
4 f/ z1 Y. K# {, C. ?3 k8 t; Z - const myContract = blockchain.openContract(
4 ]$ l; J5 i3 R' y- @ - await MainContract.createFromConfig(; j/ X; \3 R/ @- g4 R
- {! `( N2 e/ p; c! b: H# E
- number: 0,
J0 U8 ~. x& Y+ i; j - address: initAddress.address,/ V- U' c# l$ H6 \& j' u( H8 G
- owner_address: ownerAddress.address, // now we create myContract from 3 parameters
9 E8 P! n* s8 k. ]8 L1 O: S7 R& s - },5 @5 W1 k* J) z* ?* b* Q- P
- codeCell( I" {9 P. P: w# }4 Q
- )
! v0 U" z; T6 r+ e - );/ [/ x# R8 f, a7 {; M2 }/ f
4 j* b# c3 W* V8 t G. ?- // ...rest of testing code
复制代码
0 ^9 [& }0 N% U, S& d我们引入一个新的金库,它实际上将是合约的所有者,只有这个所有者以后才能提取资金。这些更新在 tests/main.spec.ts 中进行: - // ...library imports
1 T& o2 ~6 [ k4 U' d9 C; P1 s( X
# Q l4 x, [- M8 x* G) V- |- g7 x- describe("main.fc contract tests", () => {
9 y% U. t, V" Z# [( |2 e5 f' u - it("should get the proper most recent sender address", async () => {8 j7 Y% g4 K$ K! `, ~, m) n
- const blockchain = await Blockchain.create();
; h, [# u( m, M7 X9 O6 A. [ - const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];
% b* s9 R! Z. r2 ? - / ^% C0 m$ i; Y, Q& a
- const initAddress = await blockchain.treasury("initAddress");
8 D, P4 v |. t* | - const ownerAddress = await blockchain.treasury("ownerAddress");2 R6 m( N4 G6 Z5 k) t4 \, G
- - X, D( S' w+ }' k. ~4 ~: R
- const myContract = blockchain.openContract(
3 P) r2 O3 {5 _% I( C7 _6 C) O% Y - await MainContract.createFromConfig(
6 M$ s" {2 ?* g - {
5 `* [8 E' w* U5 N' {1 q6 E6 T9 i - number: 0,
# _- h1 Y- o7 L% C0 p: Z' N3 [, m - address: initAddress.address,/ D8 n4 Y- d F: r+ l' F
- owner_address: ownerAddress.address, // now we create myContract from 3 parameters) l4 y; e' b9 ~6 g, ]
- },7 ~' M G" b% S7 M
- codeCell) v9 y0 u0 V1 Y' l
- )
: H ~% r- j+ b5 i! j8 l - );; l* j8 Z! U1 d- Z# ^2 Z B, V* |
0 {! Q e6 ?( C5 c' d( i% `( ]- // ...rest of testing code
复制代码现在我们好了。 yarn test 成功通过我们之前编写的测试。
9 d: w5 O2 M7 @存款测试让我们在 main.spec.ts 文件中再添加几个测试。 由于我们必须在每次新测试前重新启动合约,因此我们还将把合约启动引入 Jest 的 beforeEach 函数: - // We need to additionally import SandboxContract and TreasuryContract
7 j7 Y0 f% [9 `4 `0 a1 J" X& B5 i4 M - import { Blockchain, SandboxContract, TreasuryContract } from "@ton/sandbox";
6 p7 G/ }: A' n! J - // ... other library imports: @. k" g8 Z3 c- N1 y) ^& ~
- describe("main.fc contract tests", () => {# X; Z! Z" W4 q7 p7 Q: j
- let blockchain: Blockchain;
0 B0 u# J; V' N# Q3 G - let myContract: SandboxContract<MainContract>;* J; z' v( V8 g# O4 v9 }' `7 `
- let initWallet: SandboxContract<TreasuryContract>;0 V5 p6 {; p! y* U8 o
- let ownerWallet: SandboxContract<TreasuryContract>;
' {' O, r" B! Q/ z' F - ) V9 _! `/ T* _9 l1 {
- beforeEach(async () => {
, E- l* L1 y) r! I/ e& X+ G* J - blockchain = await Blockchain.create();
' b) h& B" p2 l: [ - initWallet = await blockchain.treasury("initWallet");5 s- z/ v, E3 x8 H2 l
- ownerWallet = await blockchain.treasury("ownerWallet");
' Y. C0 ]5 \. Q" m
; M2 ?0 Q/ }7 G+ A+ f( F) {% L- const codeCell = Cell.fromBoc(Buffer.from(hex, "hex"))[0];$ @1 B! l$ c, l( j- X
- ; p- M, F0 U% Y" o7 C: C
- myContract = blockchain.openContract(8 H/ |* Y9 m* l2 S* c
- await MainContract.createFromConfig(7 }0 R, G- i/ K9 s
- {, V$ t" W) a% z' y
- number: 0,
' D3 I! y. f5 C( ^3 |! q - address: initWallet.address,
/ m* s2 S# T$ @4 Z - owner_address: ownerWallet.address,
/ y- r. m* G0 w - },# \( R% p" [ M# M4 G6 ]
- codeCell4 R& L. X& ~0 l: T$ I# [1 k k
- )6 }5 N2 y" N( E U4 E+ M
- );9 Z( a/ f# }7 \* T# ^; [
- });
* X% I. @- R3 ?: X% E0 d) O9 [
' l# Q- w: i) u: k0 }, G- it("should get the proper most recent sender address", async () => {$ v0 [6 P" J$ h
- const senderWallet = await blockchain.treasury("sender");
5 \/ u W. y! @! K; E- L. V1 t9 V - # W7 ]5 j0 ^) [& U5 H6 L8 K: O$ C" C
- const sentMessageResult = await myContract.sendIncrement(
. o1 B" ~! Z. I" P7 A2 z% v - senderWallet.getSender(),6 h& u$ J. ]! N2 k
- toNano("0.05"),
, K" c2 { T: ~, G% P5 R - 1
) T% L5 v( i8 e% i/ E - );
, g$ o. z$ T' p* \( i0 q
! k9 m$ O1 K, z& }/ p( F k- expect(sentMessageResult.transactions).toHaveTransaction({8 N6 K, I, N/ J3 F$ k
- from: senderWallet.address,
2 w, x& s; m$ P( A! x$ p8 T# X3 j2 D - to: myContract.address,$ [- n, T% h; q% S4 J( b
- success: true,* s, S; b3 `' r
- });' O3 U0 k) y+ B( \
, j: Z! F1 y) m' o5 i, W7 v! t3 Q- const data = await myContract.getData();. O6 [( u1 a: `; V: J7 a
- * m _- \* L8 t2 U! O
- expect(data.recent_sender.toString()).toBe(senderWallet.address.toString());
; h5 H }9 j) M: h4 G8 G9 x( D2 d2 ~ - expect(data.number).toEqual(1);$ h2 }- c/ }+ q* R/ @3 [
- });
4 s. ]0 n) _( e+ U p V+ | - it("successfully deposits funds", () => {
$ C2 [ D6 k; F8 K5 [' C% `1 ] - // test logic is coming
1 q8 P B0 E! D$ L - });
) e- e- U! i% N; ]) n# J - it("should return deposit funds as no command is sent", () => {
4 P/ w! c$ h: _8 c3 W5 r+ J - // test logic is coming; O+ Z! e; }7 _% n
- });8 _& L# f0 ]4 P! l& y! V
- it("successfully withdraws funds on behalf of owner", () => {
& k% I2 E% V# w5 ?5 o - // test logic is coming! W2 h6 y$ o. E
- });
; J6 {7 P* D5 O" P) O& h: L" L - it("fails to withdraw funds on behalf of non-owner", () => {
& b) k3 O9 |; `" X, q d. k% U+ F - // test logic is coming8 b! d, P8 A t( p) u; E" C# J
- });
) P$ P% J! p" ]) y - it("fails to withdraw funds because lack of balance", () => {/ W+ n0 i$ d* e* C
- // test logic is coming
) {0 @; [) F9 ^$ A - });
# I/ P( K; V& {+ Y% Y( j$ z9 c* @" J - });
复制代码我们只是将第一个测试的部分行移动到了 beforeEach 中,其余的行应留在第一个测试中 it
让我们为存款写一个测试。我们将发送一条 op == 2 的信息,然后检查余额。像往常一样,首先创建一个封装方法,我们将其称为 sendDeposit: - async sendDeposit(provider: ContractProvider, sender: Sender, value: bigint) {( Q4 @4 k, C3 L2 c! B/ q/ G) V/ m
- const msg_body = beginCell(): B* L% S7 S$ C+ j7 Q9 j8 [) V
- .storeUint(2, 32) // OP code
6 l) M- f' d( V# C0 ^' {. Z - .endCell();
# A- z( g3 N7 @1 _; D
* {# t: q- p! v- await provider.internal(sender, {
' {0 J- j. K' g. A - value,% Q# r2 T4 m( F9 z; m0 Q/ w) J) U. E. k
- sendMode: SendMode.PAY_GAS_SEPARATELY,
# \. A' N9 `5 \1 O' e- L - body: msg_body,
# M9 I4 u; z# r7 T" M5 j - });, i6 Q" ~1 @8 a) [
- }
复制代码我们将不详细介绍这种方法,因为它非常简单--我们只需在信息正文中传递一个操作码。 下面是我们如何使用该方法运行存款测试。 - it("successfully deposits funds", async () => {
; a2 d- w: d" ? U; }% N# D - const senderWallet = await blockchain.treasury("sender");- ^8 \* G3 ^4 P
# f% S5 w! D1 ?9 I8 f- const depositMessageResult = await myContract.sendDeposit(: H# M4 ^( D( i& R
- senderWallet.getSender(),
7 V5 C$ H6 {/ C, \+ L - toNano("5")3 \0 [% u9 o, G. y; d: w
- );# Y4 P% j; P1 ~3 d! R7 ]5 P4 ~2 g
3 a1 G; Q9 A9 d! z& z- expect(depositMessageResult.transactions).toHaveTransaction({3 K* ^5 w1 D. N- p" Y
- from: senderWallet.address,
; j* J2 w9 o( V! ^! g4 d* h7 k - to: myContract.address,' H, \! C: L+ g' [8 O: t
- success: true,
! e5 `, a; Z% A( B( j1 M& S' Q) } - });1 y3 _3 i* k! o
: l7 j7 B9 p. v, _3 [% ]" R. J& o- const balanceRequest = await myContract.getBalance();$ ?* z1 Q) Y) G
- : ^. v" Z; s5 |) y; ]
- expect(balanceRequest.number).toBeGreaterThan(toNano("4.99"));
4 e \4 j2 T0 E% O) H) ]/ z2 M9 n- a - });
复制代码请注意我们是如何检查合约余额是否大于 4.99 TON的,因为我们知道手续费会损失一些资金。 我们再写一个存款测试,但这次会出现误报。我们将在没有存款操作码的情况下发送资金,并期待返回交易,因为该操作码未知。 和以往一样,我们还有一个包装器: - async sendNoCodeDeposit(
$ Y0 |; L2 M; t* ^, Z1 D - provider: ContractProvider,( s# f4 G% z4 S8 ^4 v
- sender: Sender,4 s; I9 _2 X* S, T/ i& m9 _$ }/ a
- value: bigint |4 W9 F) ^' D
- ) {
: G! w3 e- {5 B% d5 n6 P - const msg_body = beginCell().endCell();
3 Y5 \) L/ j8 F* p# K
' @1 x8 s! q2 X' {* B8 ?$ W* ~- await provider.internal(sender, {* z( N$ p3 P$ V
- value,
$ u7 c- W4 C+ T1 t( b* C h - sendMode: SendMode.PAY_GAS_SEPARATELY,8 H) _5 {( ~ h6 p/ ]
- body: msg_body,. l* A% o7 Y G# s8 C
- });
3 X s e& T, l- z4 S3 J/ K - }
复制代码下面是测试代码:
) C/ { j) [& Q/ `2 @! b' C- it("should return funds as no command is sent", async () => {
7 R, W O; m, k n( h# H - const senderWallet = await blockchain.treasury("sender");
; h; v/ x. o; j - ' c/ D# j1 P4 E) w
- const depositMessageResult = await myContract.sendNoCodeDeposit(4 N1 E% @- O, _: d6 i# l
- senderWallet.getSender(),6 ^6 j T# N* d V- M U8 x
- toNano("5")/ m- k! O$ W, a# P. h$ f
- );3 p. N9 b4 X) h0 B ] t
3 w& ^& q. I/ i0 D$ e$ E ^4 q- expect(depositMessageResult.transactions).toHaveTransaction({
$ |; Q+ ]. x& I9 [3 G - from: myContract.address,
6 h6 ]& z4 i+ Y - to: senderWallet.address,
+ a' ?0 P" g9 }+ L - success: true,
! e8 |+ ?/ |- B; V - });
3 g* |5 B" P& Q! ^0 k" e% N5 }3 S# D
$ l7 _/ m a- p; P1 n- const balanceRequest = await myContract.getBalance(); V' ~' ?' `4 O$ M3 E
$ L3 c `. s+ ]- A5 M- expect(balanceRequest.number).toBe(0);/ K0 b, P [& [9 `
- });
复制代码 提款测试让我们先创建一个用于提款的新包装器: - async sendWithdrawalRequest(& c1 R Y$ `* q& k5 y
- provider: ContractProvider,
0 D. R8 t$ T# d" W% q5 ] - sender: Sender,0 k5 I- x) _2 W$ C" Z
- value: bigint,; l% i, x/ \7 l$ c4 ~
- amount: bigint
. V$ G, v. g) g - ) {" P8 T: P8 g N% D
- const msg_body = beginCell()
$ Y& I- j; v9 {5 I5 u2 n# J% ^- \ - .storeUint(3, 32) // OP code8 ]+ r- ]/ @8 K& e+ o
- .storeCoins(amount)
4 @' G" T1 i6 `8 T - .endCell();! ]7 |$ a0 j5 H
$ A2 F" h3 P; w4 O& C* n- await provider.internal(sender, {
; A" v4 m9 V9 O5 h# ], |( f! A& L - value,
2 B/ E5 n5 |% I' J0 j5 Y+ @ - sendMode: SendMode.PAY_GAS_SEPARATELY,4 b4 c& M" r4 R+ t& a A
- body: msg_body,4 {$ g! o8 `& p* d" R! H6 C4 L# U: W
- });
, q; ^3 K" G; u; S) ]9 e# J1 ~ - }
复制代码我们输入适当的 msg_body 和所需的提款 amount 。 我们将进行 3 项不同的测试。让我们一起来看看: - it("successfully withdraws funds on behalf of owner", async () => {1 p \) Y1 [# `& p# `7 E" F: t! e* S% q
- const senderWallet = await blockchain.treasury("sender");
Z3 J3 L+ ^2 X) m
3 h5 Q9 s. m! B$ k7 }) f' n- await myContract.sendDeposit(senderWallet.getSender(), toNano("5")); T b; j$ @- u5 x- d+ q
- 9 a0 h% j4 W8 k1 ^
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
3 P/ F9 a% p4 j- }7 ~7 z - ownerWallet.getSender(),% _) n" N2 k Y: Q# r
- toNano("0.05"),
/ S g8 n0 F( k2 Z+ t. G( ? - toNano("1")* A" W" Q/ e9 H5 f, q
- );: O9 C' y) h. v* W* y9 e' r5 {2 U
$ p2 Y1 ^% W! s6 A- expect(withdrawalRequestResult.transactions).toHaveTransaction({- h; ]7 q% e$ D8 @. d+ _$ o. M
- from: myContract.address,2 Y* M. L7 O/ q$ i
- to: ownerWallet.address,
+ P' J9 x9 U5 u$ J# N0 ^ - success: true,8 c! ^9 l( y) w" I$ F
- value: toNano(1),' L8 M) G5 c" `. i: K; m3 ?
- });
4 _/ D) B2 D. c% V& C. ? - });
复制代码 要成功提款,我们首先要正确存款。然后我们调用 sendWithdrawalRequest 指定我们要提取 1 Ton。请注意,0.05 Ton只是为了支付费用而指定的信息值。
( u. Q7 J* W. E/ [, Z6 o) T- it("fails to withdraw funds on behalf of not-owner", async () => {# j0 J3 w" v1 l. Q! I7 a
- const senderWallet = await blockchain.treasury("sender");
3 z, [2 P: l9 N& O$ ?9 r - ( @: f! K0 M( E
- await myContract.sendDeposit(senderWallet.getSender(), toNano("5"));
* B9 Y$ x8 r t/ }' b% O. B2 F - ; u- O$ ?: Q: \# |, j5 Z) E
- const withdrawalRequestResult = await myContract.sendWithdrawalRequest(( B. f5 ~/ N: N% e) G
- senderWallet.getSender(),& i8 \3 s l9 V8 a2 e$ n
- toNano("0.5"),
D/ l4 h6 Q. K6 @: m( S' B Y* c - toNano("1")" w! a6 ?% L9 ]% ]1 J
- );7 z2 M) _9 Y8 ~! y# P
, j0 ]- d- j' m2 R8 W2 m+ N4 [1 y, U- expect(withdrawalRequestResult.transactions).toHaveTransaction({( ?. C! R8 F5 g. y5 ?% I) Y
- from: senderWallet.address,
" k7 h' W5 f0 o* B. J - to: myContract.address,
1 a1 u" z6 K3 P1 u0 D: y P - success: false,
6 k! R& j# @0 f# u. I9 v: o& K7 {! J - exitCode: 103,
2 f( q% T6 ^3 N# ] - });, s1 i& H5 i+ T" K! j( ~* ]
- });
复制代码在本次测试中,我们代表发送方钱包而不是所有者钱包发送提款请求(两者的区别在于初始化时我们提供的助记词). 还请注意,我们是如何通过 success: false 和具体的失败原因 exitCode: 103. 为了确保我们做得正确,请看一下我们的合约代码,我们有这样一行代码: - throw_unless(103, equal_slice_bits(sender_address, owner_address));5 Q* X9 f: u7 S( W i: @& J
复制代码我们进行最后一项测试--由于没有余额,提款失败: - it("fails to withdraw funds because lack of balance", async () => {
4 \( J, W* L" Z+ _0 g' G' E - const withdrawalRequestResult = await myContract.sendWithdrawalRequest(
: ^& `9 q5 a$ T: I, n - ownerWallet.getSender(),
7 v" I+ Q2 f* W9 V - toNano("0.5"),' L; Z& b8 M( M& F2 F$ e! P
- toNano("1")
; Y; x+ {5 y; h# d8 `. N+ U - ); B, u* E T1 A6 _
- 6 K. \. ?' {# e2 @6 G
- expect(withdrawalRequestResult.transactions).toHaveTransaction({
+ A, s" h! q3 J- P9 s3 Q2 c! s - from: ownerWallet.address,/ T5 _' F( E5 |0 X
- to: myContract.address,
% f$ l" f3 n( E' _) s9 ?; m - success: false,7 X& }9 G' Q4 p' s: z4 Y
- exitCode: 104,- s- I! Q1 h% s% ]0 }: `& n7 K
- });( J+ D9 z6 }; A P, i) m g
- });
复制代码- throw_unless(104, balance >= withdraw_amount);
) D' w _# g5 n% u% R2 _2 w" M
复制代码就是这样 几章之后,我们将学习如何构建网络客户端,以便与合约进行交互。 " s5 ]2 G; b: _/ a( N
% Y, c% O7 e4 d# _) w: z |