You can not select more than 25 topics
Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.
243 lines
9.1 KiB
243 lines
9.1 KiB
2 years ago
|
;; Jetton Wallet Smart Contract
|
||
|
|
||
|
{-
|
||
|
|
||
|
NOTE that this tokens can be transferred within the same workchain.
|
||
|
|
||
|
This is suitable for most tokens, if you need tokens transferable between workchains there are two solutions:
|
||
|
|
||
|
1) use more expensive but universal function to calculate message forward fee for arbitrary destination (see `misc/forward-fee-calc.cs`)
|
||
|
|
||
|
2) use token holder proxies in target workchain (that way even 'non-universal' token can be used from any workchain)
|
||
|
|
||
|
-}
|
||
|
|
||
|
#pragma version >=0.2.0;
|
||
|
|
||
|
#include "imports/stdlib.fc";
|
||
|
#include "imports/utils.fc";
|
||
|
#include "imports/op-codes.fc";
|
||
|
|
||
|
int min_tons_for_storage() asm "10000000 PUSHINT"; ;; 0.01 TON
|
||
|
int gas_consumption() asm "10000000 PUSHINT"; ;; 0.01 TON
|
||
|
|
||
|
{-
|
||
|
Storage
|
||
|
storage#_ balance:(VarUInteger 16) owner_address:MsgAddressInt jetton_master_address:MsgAddressInt jetton_wallet_code:^Cell = Storage;
|
||
|
-}
|
||
|
|
||
|
(int, slice, slice, cell) load_data() inline {
|
||
|
slice ds = get_data().begin_parse();
|
||
|
return (ds~load_coins(), ds~load_msg_addr(), ds~load_msg_addr(), ds~load_ref());
|
||
|
}
|
||
|
|
||
|
() save_data (int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) impure inline {
|
||
|
set_data(pack_jetton_wallet_data(balance, owner_address, jetton_master_address, jetton_wallet_code));
|
||
|
}
|
||
|
|
||
|
{-
|
||
|
transfer query_id:uint64 amount:(VarUInteger 16) destination:MsgAddress
|
||
|
response_destination:MsgAddress custom_payload:(Maybe ^Cell)
|
||
|
forward_ton_amount:(VarUInteger 16) forward_payload:(Either Cell ^Cell)
|
||
|
= InternalMsgBody;
|
||
|
-}
|
||
|
|
||
|
() send_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure {
|
||
|
int query_id = in_msg_body~load_uint(64);
|
||
|
int jetton_amount = in_msg_body~load_coins();
|
||
|
slice to_owner_address = in_msg_body~load_msg_addr();
|
||
|
force_chain(to_owner_address);
|
||
|
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
||
|
balance -= jetton_amount;
|
||
|
|
||
|
throw_unless(705, equal_slices(owner_address, sender_address));
|
||
|
throw_unless(706, balance >= 0);
|
||
|
|
||
|
cell state_init = calculate_jetton_wallet_state_init(to_owner_address, jetton_master_address, jetton_wallet_code);
|
||
|
slice to_wallet_address = calculate_jetton_wallet_address(state_init);
|
||
|
slice response_address = in_msg_body~load_msg_addr();
|
||
|
cell custom_payload = in_msg_body~load_dict();
|
||
|
int forward_ton_amount = in_msg_body~load_coins();
|
||
|
throw_unless(708, slice_bits(in_msg_body) >= 1);
|
||
|
slice either_forward_payload = in_msg_body;
|
||
|
var msg = begin_cell()
|
||
|
.store_uint(0x18, 6)
|
||
|
.store_slice(to_wallet_address)
|
||
|
.store_coins(0)
|
||
|
.store_uint(4 + 2 + 1, 1 + 4 + 4 + 64 + 32 + 1 + 1 + 1)
|
||
|
.store_ref(state_init);
|
||
|
var msg_body = begin_cell()
|
||
|
.store_uint(op::internal_transfer, 32)
|
||
|
.store_uint(query_id, 64)
|
||
|
.store_coins(jetton_amount)
|
||
|
.store_slice(owner_address)
|
||
|
.store_slice(response_address)
|
||
|
.store_coins(forward_ton_amount)
|
||
|
.store_slice(either_forward_payload)
|
||
|
.end_cell();
|
||
|
|
||
|
msg = msg.store_ref(msg_body);
|
||
|
int fwd_count = forward_ton_amount ? 2 : 1;
|
||
|
throw_unless(709, msg_value >
|
||
|
forward_ton_amount +
|
||
|
;; 3 messages: wal1->wal2, wal2->owner, wal2->response
|
||
|
;; but last one is optional (it is ok if it fails)
|
||
|
fwd_count * fwd_fee +
|
||
|
(2 * gas_consumption() + min_tons_for_storage()));
|
||
|
;; universal message send fee calculation may be activated here
|
||
|
;; by using this instead of fwd_fee
|
||
|
;; msg_fwd_fee(to_wallet, msg_body, state_init, 15)
|
||
|
|
||
|
send_raw_message(msg.end_cell(), 64); ;; revert on errors
|
||
|
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
||
|
}
|
||
|
|
||
|
{-
|
||
|
internal_transfer query_id:uint64 amount:(VarUInteger 16) from:MsgAddress
|
||
|
response_address:MsgAddress
|
||
|
forward_ton_amount:(VarUInteger 16)
|
||
|
forward_payload:(Either Cell ^Cell)
|
||
|
= InternalMsgBody;
|
||
|
-}
|
||
|
|
||
|
() receive_tokens (slice in_msg_body, slice sender_address, int my_ton_balance, int fwd_fee, int msg_value) impure {
|
||
|
;; NOTE we can not allow fails in action phase since in that case there will be
|
||
|
;; no bounce. Thus check and throw in computation phase.
|
||
|
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
||
|
int query_id = in_msg_body~load_uint(64);
|
||
|
int jetton_amount = in_msg_body~load_coins();
|
||
|
balance += jetton_amount;
|
||
|
slice from_address = in_msg_body~load_msg_addr();
|
||
|
slice response_address = in_msg_body~load_msg_addr();
|
||
|
throw_unless(707,
|
||
|
equal_slices(jetton_master_address, sender_address)
|
||
|
|
|
||
|
equal_slices(calculate_user_jetton_wallet_address(from_address, jetton_master_address, jetton_wallet_code), sender_address)
|
||
|
);
|
||
|
int forward_ton_amount = in_msg_body~load_coins();
|
||
|
|
||
|
int ton_balance_before_msg = my_ton_balance - msg_value;
|
||
|
int storage_fee = min_tons_for_storage() - min(ton_balance_before_msg, min_tons_for_storage());
|
||
|
msg_value -= (storage_fee + gas_consumption());
|
||
|
if(forward_ton_amount) {
|
||
|
msg_value -= (forward_ton_amount + fwd_fee);
|
||
|
slice either_forward_payload = in_msg_body;
|
||
|
|
||
|
var msg_body = begin_cell()
|
||
|
.store_uint(op::transfer_notification, 32)
|
||
|
.store_uint(query_id, 64)
|
||
|
.store_coins(jetton_amount)
|
||
|
.store_slice(from_address)
|
||
|
.store_slice(either_forward_payload)
|
||
|
.end_cell();
|
||
|
|
||
|
var msg = begin_cell()
|
||
|
.store_uint(0x10, 6) ;; we should not bounce here cause receiver can have uninitialized contract
|
||
|
.store_slice(owner_address)
|
||
|
.store_coins(forward_ton_amount)
|
||
|
.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
||
|
.store_ref(msg_body);
|
||
|
|
||
|
send_raw_message(msg.end_cell(), 1);
|
||
|
}
|
||
|
|
||
|
if ((response_address.preload_uint(2) != 0) & (msg_value > 0)) {
|
||
|
var msg = begin_cell()
|
||
|
.store_uint(0x10, 6) ;; nobounce - int_msg_info$0 ihr_disabled:Bool bounce:Bool bounced:Bool src:MsgAddress -> 010000
|
||
|
.store_slice(response_address)
|
||
|
.store_coins(msg_value)
|
||
|
.store_uint(0, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
||
|
.store_uint(op::excesses, 32)
|
||
|
.store_uint(query_id, 64);
|
||
|
send_raw_message(msg.end_cell(), 2);
|
||
|
}
|
||
|
|
||
|
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
||
|
}
|
||
|
|
||
|
() burn_tokens (slice in_msg_body, slice sender_address, int msg_value, int fwd_fee) impure {
|
||
|
;; NOTE we can not allow fails in action phase since in that case there will be
|
||
|
;; no bounce. Thus check and throw in computation phase.
|
||
|
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
||
|
int query_id = in_msg_body~load_uint(64);
|
||
|
int jetton_amount = in_msg_body~load_coins();
|
||
|
slice response_address = in_msg_body~load_msg_addr();
|
||
|
cell custom_payload = in_msg_body~load_dict();
|
||
|
balance -= jetton_amount;
|
||
|
throw_unless(705, equal_slices(jetton_master_address, sender_address));
|
||
|
throw_unless(706, balance >= 0);
|
||
|
throw_unless(707, msg_value > fwd_fee + 2 * gas_consumption());
|
||
|
|
||
|
var msg_body = begin_cell()
|
||
|
.store_uint(op::burn_notification, 32)
|
||
|
.store_uint(query_id, 64)
|
||
|
.store_coins(jetton_amount)
|
||
|
.store_slice(owner_address)
|
||
|
.store_slice(response_address)
|
||
|
.store_dict(custom_payload)
|
||
|
.end_cell();
|
||
|
|
||
|
var msg = begin_cell()
|
||
|
.store_uint(0x18, 6)
|
||
|
.store_slice(jetton_master_address)
|
||
|
.store_coins(0)
|
||
|
.store_uint(1, 1 + 4 + 4 + 64 + 32 + 1 + 1)
|
||
|
.store_ref(msg_body);
|
||
|
|
||
|
send_raw_message(msg.end_cell(), 64);
|
||
|
|
||
|
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
||
|
}
|
||
|
|
||
|
() on_bounce (slice in_msg_body) impure {
|
||
|
in_msg_body~skip_bits(32); ;; 0xFFFFFFFF
|
||
|
(int balance, slice owner_address, slice jetton_master_address, cell jetton_wallet_code) = load_data();
|
||
|
int op = in_msg_body~load_uint(32);
|
||
|
throw_unless(709, (op == op::internal_transfer) | (op == op::burn_notification));
|
||
|
int query_id = in_msg_body~load_uint(64);
|
||
|
int jetton_amount = in_msg_body~load_coins();
|
||
|
balance += jetton_amount;
|
||
|
save_data(balance, owner_address, jetton_master_address, jetton_wallet_code);
|
||
|
}
|
||
|
|
||
|
() recv_internal(int my_balance, int msg_value, cell in_msg_full, slice in_msg_body) impure {
|
||
|
if (in_msg_body.slice_empty?()) { ;; ignore empty messages
|
||
|
return ();
|
||
|
}
|
||
|
|
||
|
slice cs = in_msg_full.begin_parse();
|
||
|
int flags = cs~load_uint(4);
|
||
|
if (flags & 1) {
|
||
|
on_bounce(in_msg_body);
|
||
|
return ();
|
||
|
}
|
||
|
slice sender_address = cs~load_msg_addr();
|
||
|
cs~load_msg_addr(); ;; skip dst
|
||
|
cs~load_coins(); ;; skip value
|
||
|
cs~skip_bits(1); ;; skip extracurrency collection
|
||
|
cs~load_coins(); ;; skip ihr_fee
|
||
|
int fwd_fee = cs~load_coins(); ;; we use message fwd_fee for estimation of forward_payload costs
|
||
|
|
||
|
int op = in_msg_body~load_uint(32);
|
||
|
|
||
|
if (op == op::transfer) { ;; outgoing transfer
|
||
|
send_tokens(in_msg_body, sender_address, msg_value, fwd_fee);
|
||
|
return ();
|
||
|
}
|
||
|
|
||
|
if (op == op::internal_transfer) { ;; incoming transfer
|
||
|
receive_tokens(in_msg_body, sender_address, my_balance, fwd_fee, msg_value);
|
||
|
return ();
|
||
|
}
|
||
|
|
||
|
if (op == op::burn) { ;; burn
|
||
|
burn_tokens(in_msg_body, sender_address, msg_value, fwd_fee);
|
||
|
return ();
|
||
|
}
|
||
|
|
||
|
throw(0xffff);
|
||
|
}
|
||
|
|
||
|
(int, slice, slice, cell) get_wallet_data() method_id {
|
||
|
return load_data();
|
||
|
}
|