@layerzerolabs/layerzero-v2-ton 3.0.12-ton.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +67 -0
- package/README.md +15 -0
- package/build/AllStorages.boc +0 -0
- package/build/AllStorages.compiled.json +1 -0
- package/build/AllStorages.fif +4164 -0
- package/build/AllStorages.test.boc +0 -0
- package/build/AllStorages.test.compiled.json +1 -0
- package/build/AllStorages.test.fif +1831 -0
- package/build/BaseContract.test.boc +0 -0
- package/build/BaseContract.test.compiled.json +1 -0
- package/build/BaseContract.test.fif +3553 -0
- package/build/Channel.boc +0 -0
- package/build/Channel.compiled.json +1 -0
- package/build/Channel.fif +5001 -0
- package/build/Channel.permissions.test.boc +0 -0
- package/build/Channel.permissions.test.compiled.json +1 -0
- package/build/Channel.permissions.test.fif +7569 -0
- package/build/ChannelBurn.test.boc +0 -0
- package/build/ChannelBurn.test.compiled.json +1 -0
- package/build/ChannelBurn.test.fif +7454 -0
- package/build/ChannelCommitPacket.test.boc +0 -0
- package/build/ChannelCommitPacket.test.compiled.json +1 -0
- package/build/ChannelCommitPacket.test.fif +7981 -0
- package/build/ChannelConfig.test.boc +0 -0
- package/build/ChannelConfig.test.compiled.json +1 -0
- package/build/ChannelConfig.test.fif +7442 -0
- package/build/ChannelInitialize.test.boc +0 -0
- package/build/ChannelInitialize.test.compiled.json +1 -0
- package/build/ChannelInitialize.test.fif +7289 -0
- package/build/ChannelMsglibIntegration.test.boc +0 -0
- package/build/ChannelMsglibIntegration.test.compiled.json +1 -0
- package/build/ChannelMsglibIntegration.test.fif +7404 -0
- package/build/ChannelMsglibSendCallback.test.boc +0 -0
- package/build/ChannelMsglibSendCallback.test.compiled.json +1 -0
- package/build/ChannelMsglibSendCallback.test.fif +7711 -0
- package/build/ChannelNilify.test.boc +0 -0
- package/build/ChannelNilify.test.compiled.json +1 -0
- package/build/ChannelNilify.test.fif +7672 -0
- package/build/ChannelReceive.test.boc +0 -0
- package/build/ChannelReceive.test.compiled.json +1 -0
- package/build/ChannelReceive.test.fif +7702 -0
- package/build/ChannelReceiveCallback.test.boc +0 -0
- package/build/ChannelReceiveCallback.test.compiled.json +1 -0
- package/build/ChannelReceiveCallback.test.fif +7549 -0
- package/build/ChannelReceiveView.test.boc +0 -0
- package/build/ChannelReceiveView.test.compiled.json +1 -0
- package/build/ChannelReceiveView.test.fif +7352 -0
- package/build/ChannelSend.test.boc +0 -0
- package/build/ChannelSend.test.compiled.json +1 -0
- package/build/ChannelSend.test.fif +7658 -0
- package/build/Classlib.test.boc +0 -0
- package/build/Classlib.test.compiled.json +1 -0
- package/build/Classlib.test.fif +4728 -0
- package/build/Connection.boc +0 -0
- package/build/Connection.compiled.json +1 -0
- package/build/Connection.fif +3503 -0
- package/build/Connection.test.boc +0 -0
- package/build/Connection.test.compiled.json +1 -0
- package/build/Connection.test.fif +6575 -0
- package/build/Controller.assertions.test.boc +0 -0
- package/build/Controller.assertions.test.compiled.json +1 -0
- package/build/Controller.assertions.test.fif +6130 -0
- package/build/Controller.boc +0 -0
- package/build/Controller.compiled.json +1 -0
- package/build/Controller.fif +3195 -0
- package/build/Controller.permissions.test.boc +0 -0
- package/build/Controller.permissions.test.compiled.json +1 -0
- package/build/Controller.permissions.test.fif +6237 -0
- package/build/Controller.test.boc +0 -0
- package/build/Controller.test.compiled.json +1 -0
- package/build/Controller.test.fif +6400 -0
- package/build/Counter.boc +0 -0
- package/build/Counter.compiled.json +1 -0
- package/build/Counter.fif +4809 -0
- package/build/Counter.permissions.test.boc +0 -0
- package/build/Counter.permissions.test.compiled.json +1 -0
- package/build/Counter.permissions.test.fif +7106 -0
- package/build/Counter.setters.test.boc +0 -0
- package/build/Counter.setters.test.compiled.json +1 -0
- package/build/Counter.setters.test.fif +7083 -0
- package/build/Counter.test.boc +0 -0
- package/build/Counter.test.compiled.json +1 -0
- package/build/Counter.test.fif +7540 -0
- package/build/Dvn.boc +0 -0
- package/build/Dvn.compiled.json +1 -0
- package/build/Dvn.fif +2923 -0
- package/build/Dvn.test.boc +0 -0
- package/build/Dvn.test.compiled.json +1 -0
- package/build/Dvn.test.fif +5753 -0
- package/build/Endpoint.boc +0 -0
- package/build/Endpoint.compiled.json +1 -0
- package/build/Endpoint.fif +3694 -0
- package/build/Endpoint.permissions.test.boc +0 -0
- package/build/Endpoint.permissions.test.compiled.json +1 -0
- package/build/Endpoint.permissions.test.fif +6211 -0
- package/build/Endpoint.test.boc +0 -0
- package/build/Endpoint.test.compiled.json +1 -0
- package/build/Endpoint.test.fif +6899 -0
- package/build/EndpointSetEpConfigDefaults.test.boc +0 -0
- package/build/EndpointSetEpConfigDefaults.test.compiled.json +1 -0
- package/build/EndpointSetEpConfigDefaults.test.fif +6529 -0
- package/build/Executor.boc +0 -0
- package/build/Executor.compiled.json +1 -0
- package/build/Executor.fif +2731 -0
- package/build/Executor.test.boc +0 -0
- package/build/Executor.test.compiled.json +1 -0
- package/build/Executor.test.fif +5822 -0
- package/build/LzClasses.test.boc +0 -0
- package/build/LzClasses.test.compiled.json +1 -0
- package/build/LzClasses.test.fif +4457 -0
- package/build/LzUtil.test.boc +0 -0
- package/build/LzUtil.test.compiled.json +1 -0
- package/build/LzUtil.test.fif +1831 -0
- package/build/MsgData.test.boc +0 -0
- package/build/MsgData.test.compiled.json +1 -0
- package/build/MsgData.test.fif +4318 -0
- package/build/MsglibPacketCodec.test.boc +0 -0
- package/build/MsglibPacketCodec.test.compiled.json +1 -0
- package/build/MsglibPacketCodec.test.fif +4851 -0
- package/build/MultiSig.boc +0 -0
- package/build/MultiSig.compiled.json +1 -0
- package/build/MultiSig.fif +727 -0
- package/build/MultiSigOrder.boc +0 -0
- package/build/MultiSigOrder.compiled.json +1 -0
- package/build/MultiSigOrder.fif +650 -0
- package/build/PipelinedOutOfOrder.test.boc +0 -0
- package/build/PipelinedOutOfOrder.test.compiled.json +1 -0
- package/build/PipelinedOutOfOrder.test.fif +2188 -0
- package/build/SmlConnection.boc +0 -0
- package/build/SmlConnection.compiled.json +1 -0
- package/build/SmlConnection.fif +2517 -0
- package/build/SmlConnection.permissions.test.boc +0 -0
- package/build/SmlConnection.permissions.test.compiled.json +1 -0
- package/build/SmlConnection.permissions.test.fif +5497 -0
- package/build/SmlConnection.test.boc +0 -0
- package/build/SmlConnection.test.compiled.json +1 -0
- package/build/SmlConnection.test.fif +5494 -0
- package/build/SmlManager.boc +0 -0
- package/build/SmlManager.compiled.json +1 -0
- package/build/SmlManager.fif +3904 -0
- package/build/SmlManager.permissions.test.boc +0 -0
- package/build/SmlManager.permissions.test.compiled.json +1 -0
- package/build/SmlManager.permissions.test.fif +6018 -0
- package/build/SmlManager.test.boc +0 -0
- package/build/SmlManager.test.compiled.json +1 -0
- package/build/SmlManager.test.fif +6047 -0
- package/build/Uln.boc +0 -0
- package/build/Uln.compiled.json +1 -0
- package/build/Uln.fif +4841 -0
- package/build/Uln.test.boc +0 -0
- package/build/Uln.test.compiled.json +1 -0
- package/build/Uln.test.fif +7077 -0
- package/build/UlnManager.boc +0 -0
- package/build/UlnManager.compiled.json +1 -0
- package/build/UlnManager.fif +3851 -0
- package/build/UlnManager.test.boc +0 -0
- package/build/UlnManager.test.compiled.json +1 -0
- package/build/UlnManager.test.fif +6571 -0
- package/build/UlnReceiveConfig.test.boc +0 -0
- package/build/UlnReceiveConfig.test.compiled.json +1 -0
- package/build/UlnReceiveConfig.test.fif +4413 -0
- package/build/UlnSend.test.boc +0 -0
- package/build/UlnSend.test.compiled.json +1 -0
- package/build/UlnSend.test.fif +6576 -0
- package/build/UlnSendConfig.test.boc +0 -0
- package/build/UlnSendConfig.test.compiled.json +1 -0
- package/build/UlnSendConfig.test.fif +4431 -0
- package/build/UlnSendWorkerFactory.test.boc +0 -0
- package/build/UlnSendWorkerFactory.test.compiled.json +1 -0
- package/build/UlnSendWorkerFactory.test.fif +6683 -0
- package/build/UlnUtil.test.boc +0 -0
- package/build/UlnUtil.test.compiled.json +1 -0
- package/build/UlnUtil.test.fif +5873 -0
- package/build/WorkerCore.test.boc +0 -0
- package/build/WorkerCore.test.compiled.json +1 -0
- package/build/WorkerCore.test.fif +5630 -0
- package/build/ZroMinter.boc +0 -0
- package/build/ZroMinter.compiled.json +1 -0
- package/build/ZroMinter.fif +2300 -0
- package/build/ZroWallet.boc +0 -0
- package/build/ZroWallet.compiled.json +1 -0
- package/build/ZroWallet.fif +2471 -0
- package/package.json +64 -0
- package/src/classes/lz/Attestation.fc +23 -0
- package/src/classes/lz/Config.fc +23 -0
- package/src/classes/lz/EpConfig.fc +91 -0
- package/src/classes/lz/MsglibInfo.fc +31 -0
- package/src/classes/lz/Packet.fc +202 -0
- package/src/classes/lz/Path.fc +56 -0
- package/src/classes/lz/ReceiveEpConfig.fc +24 -0
- package/src/classes/lz/SendEpConfig.fc +18 -0
- package/src/classes/lz/SmlJobAssigned.fc +20 -0
- package/src/classes/lz/Worker.fc +32 -0
- package/src/classes/msgdata/AddMsglib.fc +18 -0
- package/src/classes/msgdata/Amount.fc +16 -0
- package/src/classes/msgdata/Bool.fc +16 -0
- package/src/classes/msgdata/ChannelNonceInfo.fc +18 -0
- package/src/classes/msgdata/ClaimUnaccountedPoolFunds.fc +0 -0
- package/src/classes/msgdata/CoinsAmount.fc +16 -0
- package/src/classes/msgdata/CounterIncrement.fc +24 -0
- package/src/classes/msgdata/Deploy.fc +20 -0
- package/src/classes/msgdata/ExtendedMd.fc +20 -0
- package/src/classes/msgdata/GetMsglibCallback.fc +18 -0
- package/src/classes/msgdata/InitEndpoint.fc +16 -0
- package/src/classes/msgdata/InitSmlConnection.fc +16 -0
- package/src/classes/msgdata/InitUlnConnection.fc +18 -0
- package/src/classes/msgdata/LzReceiveStatus.fc +58 -0
- package/src/classes/msgdata/LzSend.fc +58 -0
- package/src/classes/msgdata/MdAddress.fc +18 -0
- package/src/classes/msgdata/MdEid.fc +18 -0
- package/src/classes/msgdata/MdObj.fc +18 -0
- package/src/classes/msgdata/MessagingReceipt.fc +22 -0
- package/src/classes/msgdata/MsglibSendCallback.fc +113 -0
- package/src/classes/msgdata/Nonce.fc +16 -0
- package/src/classes/msgdata/OptionsExtended.fc +20 -0
- package/src/classes/msgdata/OptionsV1.fc +27 -0
- package/src/classes/msgdata/OptionsV2.fc +34 -0
- package/src/classes/msgdata/PacketId.fc +18 -0
- package/src/classes/msgdata/PacketSent.fc +39 -0
- package/src/classes/msgdata/SetAddress.fc +16 -0
- package/src/classes/msgdata/SetEpConfig.fc +33 -0
- package/src/classes/msgdata/SetPeer.fc +18 -0
- package/src/classes/msgdata/SetSmlManagerConfig.fc +18 -0
- package/src/funC++/abstract/contractMainAbstract.fc +3 -0
- package/src/funC++/abstract/handlerAbstract.fc +12 -0
- package/src/funC++/actions/call.fc +51 -0
- package/src/funC++/actions/deploy.fc +118 -0
- package/src/funC++/actions/destroy.fc +28 -0
- package/src/funC++/actions/dispatch.fc +57 -0
- package/src/funC++/actions/event.fc +69 -0
- package/src/funC++/actions/payment.fc +52 -0
- package/src/funC++/actions/sendJettons.fc +76 -0
- package/src/funC++/actions/utils.fc +49 -0
- package/src/funC++/baseInterface.fc +16 -0
- package/src/funC++/classlib.fc +819 -0
- package/src/funC++/constants.fc +64 -0
- package/src/funC++/contractMain.fc +84 -0
- package/src/funC++/dataStructures/DeterministicInsertionCircularQueue.fc +155 -0
- package/src/funC++/dataStructures/PipelinedOutOfOrder.fc +93 -0
- package/src/funC++/handlerCore.fc +30 -0
- package/src/funC++/stdlib.fc +625 -0
- package/src/funC++/stringlib.fc +75 -0
- package/src/funC++/txnContext.fc +126 -0
- package/src/funC++/utils.fc +119 -0
- package/src/jettons/zro/minter.fc +120 -0
- package/src/jettons/zro/op-codes.fc +10 -0
- package/src/jettons/zro/params.fc +18 -0
- package/src/jettons/zro/utils.fc +33 -0
- package/src/jettons/zro/wallet.fc +261 -0
- package/tests/baseContractTest.fc +192 -0
- package/tests/testMain.fc +135 -0
|
@@ -0,0 +1,261 @@
|
|
|
1
|
+
#include "op-codes.fc";
|
|
2
|
+
#include "utils.fc";
|
|
3
|
+
#include "../../../node_modules/@ston-fi/funcbox/autoload.fc";
|
|
4
|
+
|
|
5
|
+
;; Jetton Wallet Smart Contract
|
|
6
|
+
|
|
7
|
+
{-
|
|
8
|
+
|
|
9
|
+
NOTE that this tokens can be transferred within the same workchain.
|
|
10
|
+
|
|
11
|
+
This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions:
|
|
12
|
+
|
|
13
|
+
1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`)
|
|
14
|
+
|
|
15
|
+
2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain)
|
|
16
|
+
|
|
17
|
+
-}
|
|
18
|
+
|
|
19
|
+
int min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON
|
|
20
|
+
;; Note that 2 * gas_consumptions is expected to be able to cover fees on both wallets (sender and receiver)
|
|
21
|
+
;; and also constant fees on inter-wallet interaction, in particular fwd fee on state_init transfer
|
|
22
|
+
;; that means that you need to reconsider this fee when:
|
|
23
|
+
;; a) jetton logic become more gas-heavy
|
|
24
|
+
;; b) jetton-wallet code (sent with inter-wallet message) become larger or smaller
|
|
25
|
+
;; c) global fee changes / different workchain
|
|
26
|
+
int gas_consumption() asm "15000000 PUSHINT"; ;; 0.015 TON
|
|
27
|
+
|
|
28
|
+
{-
|
|
29
|
+
Storage
|
|
30
|
+
storage#_ balance:Coins owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage;
|
|
31
|
+
-}
|
|
32
|
+
|
|
33
|
+
(int, slice, slice, cell) load_data() inline {
|
|
34
|
+
slice ds = get_data().begin_parse();
|
|
35
|
+
return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref());
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
() save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline {
|
|
39
|
+
set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code));
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
{-
|
|
43
|
+
transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
|
|
44
|
+
response_destination:MsgAddress custom_payload:(Maybe ^Cell)
|
|
45
|
+
forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
|
|
46
|
+
= InternalMsgBody;
|
|
47
|
+
internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress
|
|
48
|
+
response_address:MsgAddress
|
|
49
|
+
forward_ton_amount:(VarUInteger 16)
|
|
50
|
+
forward_payload:(Either Cell ^Cell)
|
|
51
|
+
= InternalMsgBody;
|
|
52
|
+
-}
|
|
53
|
+
|
|
54
|
+
() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure {
|
|
55
|
+
int query_id = in_msg_body~load_uint(64);
|
|
56
|
+
int jetton_amount = in_msg_body~load_coins();
|
|
57
|
+
slice to_owner_address = in_msg_body~load_msg_addr();
|
|
58
|
+
force_chain(BASECHAIN, to_owner_address, 101);
|
|
59
|
+
|
|
60
|
+
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
|
61
|
+
balance -= jetton_amount;
|
|
62
|
+
|
|
63
|
+
throw_unless(705, equal_slices(owner_address, sender_address));
|
|
64
|
+
throw_unless(706, balance >= 0);
|
|
65
|
+
|
|
66
|
+
cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code);
|
|
67
|
+
slice to_wallet_address = calculate_jetton_wallet_address(state_init);
|
|
68
|
+
slice response_address = in_msg_body~load_msg_addr();
|
|
69
|
+
cell custom_payload = in_msg_body~load_dict();
|
|
70
|
+
int forward_ton_amount = in_msg_body~load_coins();
|
|
71
|
+
throw_unless(708, slice_bits(in_msg_body) >= 1);
|
|
72
|
+
slice either_forward_payload = in_msg_body;
|
|
73
|
+
var msg = begin_cell()
|
|
74
|
+
.store_uint(0x18, 6)
|
|
75
|
+
.store_slice(to_wallet_address)
|
|
76
|
+
.store_coins(0)
|
|
77
|
+
.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
|
|
78
|
+
.store_ref(state_init);
|
|
79
|
+
var msg_body = begin_cell()
|
|
80
|
+
.store_uint(op::internal_transfer(), 32)
|
|
81
|
+
.store_uint(query_id, 64)
|
|
82
|
+
.store_coins(jetton_amount)
|
|
83
|
+
.store_slice(owner_address)
|
|
84
|
+
.store_slice(response_address)
|
|
85
|
+
.store_coins(forward_ton_amount)
|
|
86
|
+
.store_slice(either_forward_payload)
|
|
87
|
+
.end_cell();
|
|
88
|
+
|
|
89
|
+
msg = msg.store_ref(msg_body);
|
|
90
|
+
int fwd_count = forward_ton_amount ? 2 : 1;
|
|
91
|
+
throw_unless(709, msg_value >
|
|
92
|
+
forward_ton_amount +
|
|
93
|
+
;; 3 messages: wal1->wal2, wal2->owner, wal2->response
|
|
94
|
+
;; but last one is optional (it is ok if it fails)
|
|
95
|
+
fwd_count * fwd_fee +
|
|
96
|
+
(2 * gas_consumption() + min_tons_for_storage()));
|
|
97
|
+
;; universal message send fee calculation may be activated here
|
|
98
|
+
;; by using this instead of fwd_fee
|
|
99
|
+
;; msg_fwd_fee(to_wallet, msg_body, state_init, 15)
|
|
100
|
+
|
|
101
|
+
send_raw_message(msg.end_cell(), 64); ;; revert on errors
|
|
102
|
+
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
{-
|
|
106
|
+
internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress
|
|
107
|
+
response_address:MsgAddress
|
|
108
|
+
forward_ton_amount:(VarUInteger 16)
|
|
109
|
+
forward_payload:(Either Cell ^Cell)
|
|
110
|
+
= InternalMsgBody;
|
|
111
|
+
-}
|
|
112
|
+
|
|
113
|
+
() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure {
|
|
114
|
+
;; NOTE we can not allow fails in action phase since in that case there will be
|
|
115
|
+
;; no bounce. Thus check and throw in computation phase.
|
|
116
|
+
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
|
117
|
+
int query_id = in_msg_body~load_uint(64);
|
|
118
|
+
int jetton_amount = in_msg_body~load_coins();
|
|
119
|
+
balance += jetton_amount;
|
|
120
|
+
slice from_address = in_msg_body~load_msg_addr();
|
|
121
|
+
slice response_address = in_msg_body~load_msg_addr();
|
|
122
|
+
throw_unless(707,
|
|
123
|
+
equal_slices(jetton_master_address, sender_address)
|
|
124
|
+
|
|
|
125
|
+
equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address)
|
|
126
|
+
);
|
|
127
|
+
int forward_ton_amount = in_msg_body~load_coins();
|
|
128
|
+
|
|
129
|
+
int ton_balance_before_msg = my_ton_balance - msg_value;
|
|
130
|
+
int storage_fee = min_tons_for_storage() - min(ton_balance_before_msg, min_tons_for_storage());
|
|
131
|
+
msg_value -= (storage_fee + gas_consumption());
|
|
132
|
+
slice either_forward_payload = in_msg_body;
|
|
133
|
+
if(forward_ton_amount) {
|
|
134
|
+
msg_value -= (forward_ton_amount + fwd_fee);
|
|
135
|
+
|
|
136
|
+
var msg_body = begin_cell()
|
|
137
|
+
.store_uint(op::transfer_notification(), 32)
|
|
138
|
+
.store_uint(query_id, 64)
|
|
139
|
+
.store_coins(jetton_amount)
|
|
140
|
+
.store_slice(from_address)
|
|
141
|
+
.store_slice(either_forward_payload)
|
|
142
|
+
.end_cell();
|
|
143
|
+
|
|
144
|
+
var msg = begin_cell()
|
|
145
|
+
.store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract
|
|
146
|
+
.store_slice(owner_address)
|
|
147
|
+
.store_coins(forward_ton_amount)
|
|
148
|
+
.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
149
|
+
.store_ref(msg_body);
|
|
150
|
+
|
|
151
|
+
send_raw_message(msg.end_cell(), 1);
|
|
152
|
+
}
|
|
153
|
+
|
|
154
|
+
if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) {
|
|
155
|
+
|
|
156
|
+
var msg_body = begin_cell()
|
|
157
|
+
.store_uint(op::excesses, 32)
|
|
158
|
+
.store_uint(query_id, 64)
|
|
159
|
+
.store_coins(jetton_amount)
|
|
160
|
+
.store_slice(from_address)
|
|
161
|
+
.store_slice(either_forward_payload)
|
|
162
|
+
.end_cell();
|
|
163
|
+
|
|
164
|
+
var msg = begin_cell()
|
|
165
|
+
.store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000
|
|
166
|
+
.store_slice(response_address)
|
|
167
|
+
.store_coins(msg_value)
|
|
168
|
+
.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
169
|
+
.store_ref(msg_body);
|
|
170
|
+
send_raw_message(msg.end_cell(), 2);
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
() burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure {
|
|
177
|
+
;; NOTE we can not allow fails in action phase since in that case there will be
|
|
178
|
+
;; no bounce. Thus check and throw in computation phase.
|
|
179
|
+
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
|
180
|
+
int query_id = in_msg_body~load_uint(64);
|
|
181
|
+
int jetton_amount = in_msg_body~load_coins();
|
|
182
|
+
slice response_address = in_msg_body~load_msg_addr();
|
|
183
|
+
;; ignore custom payload
|
|
184
|
+
;; slice custom_payload = in_msg_body~load_dict();
|
|
185
|
+
balance -= jetton_amount;
|
|
186
|
+
throw_unless(705, equal_slices(owner_address, sender_address));
|
|
187
|
+
throw_unless(706, balance >= 0);
|
|
188
|
+
throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption());
|
|
189
|
+
|
|
190
|
+
var msg_body = begin_cell()
|
|
191
|
+
.store_uint(op::burn_notification(), 32)
|
|
192
|
+
.store_uint(query_id, 64)
|
|
193
|
+
.store_coins(jetton_amount)
|
|
194
|
+
.store_slice(owner_address)
|
|
195
|
+
.store_slice(response_address)
|
|
196
|
+
.end_cell();
|
|
197
|
+
|
|
198
|
+
var msg = begin_cell()
|
|
199
|
+
.store_uint(0x18, 6)
|
|
200
|
+
.store_slice(jetton_master_address)
|
|
201
|
+
.store_coins(0)
|
|
202
|
+
.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
|
203
|
+
.store_ref(msg_body);
|
|
204
|
+
|
|
205
|
+
send_raw_message(msg.end_cell(), 64);
|
|
206
|
+
|
|
207
|
+
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
|
208
|
+
}
|
|
209
|
+
|
|
210
|
+
() on_bounce (slice in_msg_body) impure {
|
|
211
|
+
in_msg_body~skip_bits(32); ;; 0xFFFFFFFF
|
|
212
|
+
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
|
213
|
+
int op = in_msg_body~load_uint(32);
|
|
214
|
+
throw_unless(709, (op == op::internal_transfer()) | (op == op::burn_notification()));
|
|
215
|
+
int query_id = in_msg_body~load_uint(64);
|
|
216
|
+
int jetton_amount = in_msg_body~load_coins();
|
|
217
|
+
balance += jetton_amount;
|
|
218
|
+
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
|
|
222
|
+
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
|
|
223
|
+
return ();
|
|
224
|
+
}
|
|
225
|
+
|
|
226
|
+
slice cs = in_msg_full.begin_parse();
|
|
227
|
+
int flags = cs~load_uint(4);
|
|
228
|
+
if (flags & 1) {
|
|
229
|
+
on_bounce(in_msg_body);
|
|
230
|
+
return ();
|
|
231
|
+
}
|
|
232
|
+
slice sender_address = cs~load_msg_addr();
|
|
233
|
+
cs~load_msg_addr(); ;; skip dst
|
|
234
|
+
cs~load_coins(); ;; skip value
|
|
235
|
+
cs~skip_bits(1); ;; skip extracurrency collection
|
|
236
|
+
cs~load_coins(); ;; skip ihr_fee
|
|
237
|
+
int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs
|
|
238
|
+
|
|
239
|
+
int op = in_msg_body~load_uint(32);
|
|
240
|
+
|
|
241
|
+
if (op == op::transfer()) { ;; outgoing transfer
|
|
242
|
+
send_tokens(in_msg_body, sender_address, msg_value, fwd_fee);
|
|
243
|
+
return ();
|
|
244
|
+
}
|
|
245
|
+
|
|
246
|
+
if (op == op::internal_transfer()) { ;; incoming transfer
|
|
247
|
+
receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value);
|
|
248
|
+
return ();
|
|
249
|
+
}
|
|
250
|
+
|
|
251
|
+
if (op == op::burn()) { ;; burn
|
|
252
|
+
burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee);
|
|
253
|
+
return ();
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
throw(0xffff);
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
(int, slice, slice, cell) get_wallet_data() method_id {
|
|
260
|
+
return load_data();
|
|
261
|
+
}
|
|
@@ -0,0 +1,192 @@
|
|
|
1
|
+
#include "./testMain.fc";
|
|
2
|
+
#include "../src/funC++/classlib.fc";
|
|
3
|
+
#include "../src/funC++/stringlib.fc";
|
|
4
|
+
#include "../src/funC++/utils.fc";
|
|
5
|
+
|
|
6
|
+
#include "../src/funC++/actions/event.fc";
|
|
7
|
+
#include "../src/funC++/actions/sendJettons.fc";
|
|
8
|
+
#include "../src/funC++/actions/call.fc";
|
|
9
|
+
#include "../src/funC++/actions/deploy.fc";
|
|
10
|
+
#include "../src/funC++/actions/destroy.fc";
|
|
11
|
+
#include "../src/funC++/actions/dispatch.fc";
|
|
12
|
+
#include "../src/funC++/actions/payment.fc";
|
|
13
|
+
|
|
14
|
+
;;; ===============================INTERFACE FUNCTIONS===========================
|
|
15
|
+
int _callCheckPermissions(int op, cell $md) impure;
|
|
16
|
+
|
|
17
|
+
;;; ===============================BASE TEST INTERFACE IMPLEMENTATIONS===========================
|
|
18
|
+
|
|
19
|
+
;; authenticates and initializes a base storage for an LZ contract
|
|
20
|
+
() forceAuthenticate(int base_storage_idx) impure;
|
|
21
|
+
|
|
22
|
+
cell createInitializedStorage() impure;
|
|
23
|
+
|
|
24
|
+
cell baseTest::prepare(tuple args) impure {
|
|
25
|
+
return createInitializedStorage();
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
;;; ===============================HELPER FUNCTIONS===========================
|
|
29
|
+
() spoofCaller(int address_hashpart) impure inline {
|
|
30
|
+
setCaller(address_hashpart);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
;;; ===============================TEST HANDLERS===============================
|
|
34
|
+
;; An empty test, when inserted at the top of a test list it allows initialization of storage
|
|
35
|
+
;; then returns success. Helps to avoid OOG for big contracts
|
|
36
|
+
(int, slice) initializeTestStorage(cell $storage) impure {
|
|
37
|
+
return (TEST_SUCCESS, "");
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
;; int TEST_SUCCESS or TEST_FAILURE, slice (optional) error_message
|
|
41
|
+
(int, slice) test::handler::shouldPass((cell -> tuple) handler, cell $md, tuple expectedActions, cell $expectedStorage, tuple expectedTxnContext) impure {
|
|
42
|
+
;; Checkpoint the current gas meter
|
|
43
|
+
int start = get_gas_consumed();
|
|
44
|
+
|
|
45
|
+
;; Run the actual handler
|
|
46
|
+
tuple actions = handler($md);
|
|
47
|
+
|
|
48
|
+
;; Optionally profile gas
|
|
49
|
+
if (do_profile) {
|
|
50
|
+
;; Should not inline gasConsumed because the string construction costs gas
|
|
51
|
+
int gasConsumed = get_gas_consumed() - start;
|
|
52
|
+
~strdump(base_error_msg.str::concat(" consumed gas: ").str::concatInt(gasConsumed));
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
;; Check the number of actions matches the expected
|
|
56
|
+
int numActions = actions.tlen();
|
|
57
|
+
if (numActions != expectedActions.tlen()) {
|
|
58
|
+
return (
|
|
59
|
+
TEST_FAILED,
|
|
60
|
+
"action length incorrect:"
|
|
61
|
+
.str::concatInt(actions.tlen())
|
|
62
|
+
.str::concat(" !== ")
|
|
63
|
+
.str::concatInt(expectedActions.tlen())
|
|
64
|
+
);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
if (actions.int_at(0) != expectedActions.int_at(0)) {
|
|
68
|
+
return (TEST_FAILED, "action value outflow incorrect");
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
;; Check that each action matches the expected action
|
|
72
|
+
int index = 1;
|
|
73
|
+
tuple terminalIndices = empty_tuple();
|
|
74
|
+
while (index < numActions) {
|
|
75
|
+
tuple actualAction = actions.tuple_at(index);
|
|
76
|
+
tuple expectedAction = expectedActions.tuple_at(index);
|
|
77
|
+
if (actualAction.int_at(0) != expectedAction.int_at(0)) {
|
|
78
|
+
test::throwError("action type incorrect: ".str::concatInt(index));
|
|
79
|
+
}
|
|
80
|
+
int actionType = actualAction.int_at(0);
|
|
81
|
+
int equal = false;
|
|
82
|
+
|
|
83
|
+
if (actionType == action::destroy::NAME) {
|
|
84
|
+
terminalIndices = terminalIndices.tpush(index);
|
|
85
|
+
equal = action::destroy::equals(actualAction, expectedAction);
|
|
86
|
+
} elseif (actionType == action::deploy::NAME) {
|
|
87
|
+
terminalIndices = terminalIndices.tpush(index);
|
|
88
|
+
equal = action::deploy::equals(actualAction, expectedAction);
|
|
89
|
+
} elseif (actionType == action::call::NAME) {
|
|
90
|
+
terminalIndices = terminalIndices.tpush(index);
|
|
91
|
+
equal = action::call::equals(actualAction, expectedAction);
|
|
92
|
+
} elseif (actionType == action::dispatch::NAME) {
|
|
93
|
+
equal = action::dispatch::equals(actualAction, expectedAction);
|
|
94
|
+
} elseif (actionType == action::payment::NAME) {
|
|
95
|
+
equal = action::payment::equals(actualAction, expectedAction);
|
|
96
|
+
} elseif (actionType == action::event::NAME) {
|
|
97
|
+
equal = action::event::equals(actualAction, expectedAction);
|
|
98
|
+
} elseif (actionType == action::sendJettons::NAME) {
|
|
99
|
+
equal = action::sendJettons::equals(actualAction, expectedAction);
|
|
100
|
+
}
|
|
101
|
+
ifnot (equal) {
|
|
102
|
+
test::throwError("action incorrect: ".str::concatInt(index));
|
|
103
|
+
}
|
|
104
|
+
index += 1;
|
|
105
|
+
}
|
|
106
|
+
if (terminalIndices.tlen() > 1) {
|
|
107
|
+
test::throwError("Multiple terminal actions");
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
;; Check that the storage after running the handler matches the expected storage
|
|
111
|
+
int wrongField = compareObjectFields(getContractStorage(), $expectedStorage);
|
|
112
|
+
if (wrongField == INVALID_CLASS_MEMBER) {
|
|
113
|
+
return (
|
|
114
|
+
TEST_FAILED,
|
|
115
|
+
"Storage and expected storage not of the same type"
|
|
116
|
+
);
|
|
117
|
+
} elseif (wrongField != -1) {
|
|
118
|
+
return (
|
|
119
|
+
TEST_FAILED,
|
|
120
|
+
"malformed field ".str::concatInt(wrongField)
|
|
121
|
+
);
|
|
122
|
+
}
|
|
123
|
+
if (expectedTxnContext.tlen() != txnContext.tlen()) {
|
|
124
|
+
return (TEST_FAILED, "malformed txn context");
|
|
125
|
+
}
|
|
126
|
+
;; check context
|
|
127
|
+
int index = 0;
|
|
128
|
+
while (index < txnContext.tlen()) {
|
|
129
|
+
int mismatch = false;
|
|
130
|
+
if (txnContext.at(index).is_int()) {
|
|
131
|
+
mismatch = (txnContext.int_at(index) != expectedTxnContext.int_at(index));
|
|
132
|
+
} elseif (txnContext.at(index).is_cell()) {
|
|
133
|
+
mismatch = (txnContext.cell_at(index).cell_hash() != expectedTxnContext.cell_at(index).cell_hash());
|
|
134
|
+
} elseif (txnContext.at(index).is_slice()) {
|
|
135
|
+
mismatch = (~ txnContext.slice_at(index).equal_slices(expectedTxnContext.slice_at(index)));
|
|
136
|
+
} else {
|
|
137
|
+
mismatch = (~ txnContext.cell_at(index).cl::hash() == expectedTxnContext.cell_at(index).cl::hash());
|
|
138
|
+
}
|
|
139
|
+
if (mismatch) {
|
|
140
|
+
return (TEST_FAILED, "txn context mismatch at index".str::concatInt(index));
|
|
141
|
+
}
|
|
142
|
+
index += 1;
|
|
143
|
+
}
|
|
144
|
+
|
|
145
|
+
;; If all checks pass, return success
|
|
146
|
+
return (TEST_SUCCESS, "");
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
(int, slice) test::handler::shouldFail((cell -> tuple) fn, cell $md, int expected_error) impure {
|
|
150
|
+
int failed = false;
|
|
151
|
+
try {
|
|
152
|
+
if (fn($md).tlen() >= 0) {
|
|
153
|
+
failed = true;
|
|
154
|
+
return (TEST_FAILED, "test::handler::shouldFail never throws");
|
|
155
|
+
}
|
|
156
|
+
} catch(x, n) {
|
|
157
|
+
if (n != expected_error) {
|
|
158
|
+
return (
|
|
159
|
+
TEST_FAILED,
|
|
160
|
+
"actual error: "
|
|
161
|
+
.str::concatInt(n)
|
|
162
|
+
.str::concat(" != expected: ")
|
|
163
|
+
.str::concatInt(expected_error)
|
|
164
|
+
);
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
return (TEST_SUCCESS, "");
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
(int, slice) test::permissions::shouldPass(int op, cell $md) impure {
|
|
172
|
+
int failed = false;
|
|
173
|
+
try {
|
|
174
|
+
_callCheckPermissions(op, $md);
|
|
175
|
+
} catch(x, n) {
|
|
176
|
+
failed = true;
|
|
177
|
+
}
|
|
178
|
+
return failed ? (TEST_FAILED, "permissions check should not have thrown") : (TEST_SUCCESS, "");
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
(int, slice) test::permissions::shouldFail(int op, cell $md) impure {
|
|
182
|
+
int failed = false;
|
|
183
|
+
try {
|
|
184
|
+
_callCheckPermissions(op, $md);
|
|
185
|
+
failed = true;
|
|
186
|
+
} catch(x, n) {
|
|
187
|
+
;; Catch is a function that executes in its own context
|
|
188
|
+
;; so if you try to return here, it will actually
|
|
189
|
+
;; return execution to the try block, not the caller of test::permissions::shouldFail
|
|
190
|
+
}
|
|
191
|
+
return failed ? (TEST_FAILED, "permissions check should have thrown") : (TEST_SUCCESS, "");
|
|
192
|
+
}
|
|
@@ -0,0 +1,135 @@
|
|
|
1
|
+
#include "../src/funC++/classlib.fc";
|
|
2
|
+
#include "../src/funC++/txnContext.fc";
|
|
3
|
+
#include "../src/funC++/utils.fc";
|
|
4
|
+
|
|
5
|
+
global slice base_error_msg;
|
|
6
|
+
;; const int do_profile = -1; ;; doesn't compile with true
|
|
7
|
+
const int do_profile = 0; ;; doesn't compile with false
|
|
8
|
+
|
|
9
|
+
const int TEST_SUCCESS = -1;
|
|
10
|
+
const int TEST_FAILED = 0;
|
|
11
|
+
|
|
12
|
+
const int TEST_FN_IDX = 0;
|
|
13
|
+
const int TEST_NAME_IDX = 1;
|
|
14
|
+
|
|
15
|
+
;;; ===============================INTERFACE FUNCTIONS===========================
|
|
16
|
+
slice _testName();
|
|
17
|
+
tuple baseTest::getTests() impure;
|
|
18
|
+
cell baseTest::prepare(tuple testCase) impure inline;
|
|
19
|
+
|
|
20
|
+
;;; ===============================HELPER FUNCTIONS===========================
|
|
21
|
+
const int DONE = 1;
|
|
22
|
+
() emitDone() impure inline {
|
|
23
|
+
var msg = begin_cell()
|
|
24
|
+
.store_uint (12, 4) ;; ext_out_msg_info$11 src:MsgAddressInt ()
|
|
25
|
+
.store_uint (1, 2)
|
|
26
|
+
.store_uint (256, 9)
|
|
27
|
+
.store_uint(DONE, 256)
|
|
28
|
+
.store_uint(0, 64 + 32 + 2) ;; created_lt, created_at, init:Maybe, body:Either
|
|
29
|
+
.end_cell();
|
|
30
|
+
send_raw_message(msg, 0);
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
() test::throwError(slice msg) impure {
|
|
34
|
+
~strdump(_testName()
|
|
35
|
+
.str::concat(": ")
|
|
36
|
+
.str::concat(base_error_msg)
|
|
37
|
+
.str::concat(": ")
|
|
38
|
+
.str::concat(msg)
|
|
39
|
+
);
|
|
40
|
+
throwError(
|
|
41
|
+
_testName()
|
|
42
|
+
.str::concat(": ")
|
|
43
|
+
.str::concat(base_error_msg)
|
|
44
|
+
.str::concat(": ")
|
|
45
|
+
.str::concat(msg)
|
|
46
|
+
);
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
() test::throwErrorUnless(int condition, slice msg) impure {
|
|
50
|
+
ifnot (condition) {
|
|
51
|
+
test::throwError(msg);
|
|
52
|
+
}
|
|
53
|
+
}
|
|
54
|
+
|
|
55
|
+
slice get_test_name(int index) impure method_id {
|
|
56
|
+
tuple tests = baseTest::getTests();
|
|
57
|
+
tuple testCase = tests.at(index);
|
|
58
|
+
return testCase.slice_at(TEST_NAME_IDX);
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
;;; ===============================Txn Context Manipulation===========================
|
|
62
|
+
|
|
63
|
+
() setCaller(int caller) impure inline {
|
|
64
|
+
txnContext~tset(_CALLER, caller);
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
() setContractBalance(int balance) impure inline {
|
|
68
|
+
txnContext~tset(_BALANCE, balance);
|
|
69
|
+
}
|
|
70
|
+
|
|
71
|
+
;;; Returns the current value of `c7`.
|
|
72
|
+
tuple _get_c7() impure asm "c7 PUSH";
|
|
73
|
+
|
|
74
|
+
;;; Updates the current value of `c7`.
|
|
75
|
+
() _set_c7(tuple c) impure asm "c7 POP";
|
|
76
|
+
|
|
77
|
+
;; *Testing function only. Do not use in production code!*
|
|
78
|
+
() setNewTime(int newTime) impure {
|
|
79
|
+
tuple c7 = _get_c7();
|
|
80
|
+
_set_c7(
|
|
81
|
+
c7.tset(0,
|
|
82
|
+
c7.tuple_at(0)
|
|
83
|
+
.tset(3, newTime)
|
|
84
|
+
)
|
|
85
|
+
);
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
|
|
89
|
+
;;; ===============================MAIN DRIVER FOR ALL TESTS===========================
|
|
90
|
+
() main(int myBalance, int msgValue, cell inMsgFull, slice inMsgBody) impure {
|
|
91
|
+
;; ignore empty messages
|
|
92
|
+
if (inMsgBody.slice_empty?()) {
|
|
93
|
+
return ();
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
initTxnContext(
|
|
97
|
+
myBalance,
|
|
98
|
+
msgValue,
|
|
99
|
+
inMsgFull,
|
|
100
|
+
inMsgBody
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
base_error_msg = "base_error_msg NOT set... ";
|
|
104
|
+
|
|
105
|
+
int index = getOpcode();
|
|
106
|
+
|
|
107
|
+
;; Allow the test framework to prank arbitrary caller
|
|
108
|
+
setCaller(getOrigin());
|
|
109
|
+
|
|
110
|
+
tuple tests = baseTest::getTests();
|
|
111
|
+
if (index >= tests.tlen()) {
|
|
112
|
+
emitDone();
|
|
113
|
+
} else {
|
|
114
|
+
tuple testCase = tests.at(index);
|
|
115
|
+
var test_fn = testCase.at(TEST_FN_IDX);
|
|
116
|
+
base_error_msg = testCase.at(TEST_NAME_IDX);
|
|
117
|
+
cell input = baseTest::prepare(testCase);
|
|
118
|
+
cell $storage = getContractStorage();
|
|
119
|
+
(int success, slice reason) = test_fn(input);
|
|
120
|
+
setContractStorage($storage);
|
|
121
|
+
test::throwErrorUnless(success == TEST_SUCCESS, reason);
|
|
122
|
+
}
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
(int, slice) test::shouldBeTrue(int condition) {
|
|
126
|
+
ifnot (condition) {
|
|
127
|
+
return (TEST_FAILED, "test::shouldBeTrue");
|
|
128
|
+
} else {
|
|
129
|
+
return (TEST_SUCCESS, "");
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
(int, slice) test::shouldBeFalse(int condition) {
|
|
134
|
+
return test::shouldBeTrue(condition == false);
|
|
135
|
+
}
|