Ton
250 строк · 9.5 Кб
1;; Jetton Wallet Smart Contract
2
3#include "imports/stdlib.fc";
4#include "imports/params.fc";
5#include "imports/constants.fc";
6#include "imports/jetton-utils.fc";
7#include "imports/op-codes.fc";
8#include "imports/utils.fc";
9#pragma version >=0.2.0;
10
11{-
12
13NOTE that this tokens can be transferred within the same workchain.
14
15This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions:
16
171) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`)
18
192) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain)
20
21-}
22
23const min_tons_for_storage = 10000000; ;; 0.01 TON
24const gas_consumption = 10000000; ;; 0.01 TON
25
26{-
27Storage
28storage#_ balance:Coins owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage;
29-}
30
31(int, slice, slice, cell) load_data() inline {
32slice ds = get_data().begin_parse();
33return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref());
34}
35
36() save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline {
37set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code));
38}
39
40{-
41transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
42response_destination:MsgAddress custom_payload:(Maybe ^Cell)
43forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
44= InternalMsgBody;
45internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress
46response_address:MsgAddress
47forward_ton_amount:(VarUInteger 16)
48forward_payload:(Either Cell ^Cell)
49= InternalMsgBody;
50-}
51
52() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure {
53int query_id = in_msg_body~load_uint(64);
54int jetton_amount = in_msg_body~load_coins();
55slice to_owner_address = in_msg_body~load_msg_addr();
56force_chain(to_owner_address);
57(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
58balance -= jetton_amount;
59
60throw_unless(705, equal_slices(owner_address, sender_address));
61throw_unless(706, balance >= 0);
62
63cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code);
64slice to_wallet_address = calculate_jetton_wallet_address(state_init);
65slice response_address = in_msg_body~load_msg_addr();
66cell custom_payload = in_msg_body~load_dict();
67int forward_ton_amount = in_msg_body~load_coins();
68throw_unless(708, slice_bits(in_msg_body) >= 1);
69slice either_forward_payload = in_msg_body;
70var msg = begin_cell()
71.store_uint(0x18, 6)
72.store_slice(to_wallet_address)
73.store_coins(0)
74.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
75.store_ref(state_init);
76var msg_body = begin_cell()
77.store_uint(op::internal_transfer(), 32)
78.store_uint(query_id, 64)
79.store_coins(jetton_amount)
80.store_slice(owner_address)
81.store_slice(response_address)
82.store_coins(forward_ton_amount)
83.store_slice(either_forward_payload)
84.end_cell();
85
86msg = msg.store_ref(msg_body);
87int fwd_count = forward_ton_amount ? 2 : 1;
88throw_unless(709, msg_value >
89forward_ton_amount +
90;; 3 messages: wal1->wal2, wal2->owner, wal2->response
91;; but last one is optional (it is ok if it fails)
92fwd_count * fwd_fee +
93(2 * gas_consumption + min_tons_for_storage)); ;; TODO(shahar) ?
94;; universal message send fee calculation may be activated here
95;; by using this instead of fwd_fee
96;; msg_fwd_fee(to_wallet, msg_body, state_init, 15)
97
98send_raw_message(msg.end_cell(), 64); ;; revert on errors
99save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
100}
101
102{-
103internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress
104response_address:MsgAddress
105forward_ton_amount:(VarUInteger 16)
106forward_payload:(Either Cell ^Cell)
107= InternalMsgBody;
108-}
109
110() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure {
111;; NOTE we can not allow fails in action phase since in that case there will be
112;; no bounce. Thus check and throw in computation phase.
113(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
114int query_id = in_msg_body~load_uint(64);
115int jetton_amount = in_msg_body~load_coins();
116balance += jetton_amount;
117slice from_address = in_msg_body~load_msg_addr();
118slice response_address = in_msg_body~load_msg_addr();
119throw_unless(707,
120equal_slices(jetton_master_address, sender_address)
121|
122equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address)
123);
124int forward_ton_amount = in_msg_body~load_coins();
125
126int ton_balance_before_msg = my_ton_balance - msg_value;
127int storage_fee = min_tons_for_storage - min(ton_balance_before_msg, min_tons_for_storage);
128msg_value -= (storage_fee + gas_consumption);
129if(forward_ton_amount) {
130msg_value -= (forward_ton_amount + fwd_fee);
131slice either_forward_payload = in_msg_body;
132
133var msg_body = begin_cell()
134.store_uint(op::transfer_notification(), 32)
135.store_uint(query_id, 64)
136.store_coins(jetton_amount)
137.store_slice(from_address)
138.store_slice(either_forward_payload)
139.end_cell();
140
141var msg = begin_cell()
142.store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract
143.store_slice(owner_address)
144.store_coins(forward_ton_amount)
145.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
146.store_ref(msg_body);
147
148send_raw_message(msg.end_cell(), 1);
149}
150
151if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) {
152var msg = begin_cell()
153.store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000
154.store_slice(response_address)
155.store_coins(msg_value)
156.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
157.store_uint(op::excesses(), 32)
158.store_uint(query_id, 64);
159send_raw_message(msg.end_cell(), 2);
160}
161
162save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
163}
164
165() burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure {
166;; NOTE we can not allow fails in action phase since in that case there will be
167;; no bounce. Thus check and throw in computation phase.
168(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
169int query_id = in_msg_body~load_uint(64);
170int jetton_amount = in_msg_body~load_coins();
171slice response_address = in_msg_body~load_msg_addr();
172;; ignore custom payload
173;; slice custom_payload = in_msg_body~load_dict();
174balance -= jetton_amount;
175throw_unless(705, equal_slices(owner_address, sender_address));
176throw_unless(706, balance >= 0);
177throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption);
178
179var msg_body = begin_cell()
180.store_uint(op::burn_notification(), 32)
181.store_uint(query_id, 64)
182.store_coins(jetton_amount)
183.store_slice(owner_address)
184.store_slice(response_address)
185.end_cell();
186
187var msg = begin_cell()
188.store_uint(0x18, 6)
189.store_slice(jetton_master_address)
190.store_coins(0)
191.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
192.store_ref(msg_body);
193
194send_raw_message(msg.end_cell(), 64);
195
196save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
197}
198
199() on_bounce (slice in_msg_body) impure {
200in_msg_body~skip_bits(32); ;; 0xFFFFFFFF
201(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
202int op = in_msg_body~load_uint(32);
203throw_unless(709, (op == op::internal_transfer()) | (op == op::burn_notification()));
204int query_id = in_msg_body~load_uint(64);
205int jetton_amount = in_msg_body~load_coins();
206balance += jetton_amount;
207save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
208}
209
210() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
211if (in_msg_body.slice_empty?()) { ;; ignore empty messages
212return ();
213}
214
215slice cs = in_msg_full.begin_parse();
216int flags = cs~load_uint(4);
217if (flags & 1) {
218on_bounce(in_msg_body);
219return ();
220}
221slice sender_address = cs~load_msg_addr();
222cs~load_msg_addr(); ;; skip dst
223cs~load_coins(); ;; skip value
224cs~skip_bits(1); ;; skip extracurrency collection
225cs~load_coins(); ;; skip ihr_fee
226int fwd_fee = muldiv(cs~load_coins(), 3, 2); ;; we use message fwd_fee for estimation of forward_payload costs
227
228int op = in_msg_body~load_uint(32);
229
230if (op == op::transfer()) { ;; outgoing transfer
231send_tokens(in_msg_body, sender_address, msg_value, fwd_fee);
232return ();
233}
234
235if (op == op::internal_transfer()) { ;; incoming transfer
236receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value);
237return ();
238}
239
240if (op == op::burn()) { ;; burn
241burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee);
242return ();
243}
244
245throw(0xffff);
246}
247
248(int, slice, slice, cell) get_wallet_data() method_id {
249return load_data();
250}
251