Skip to content

Commit 8d0fa45

Browse files
Merge pull request #7 from ProgramCrafter/func-untested
A monolith commit implementing DEX in FunC
2 parents 2df63bf + 25aedb8 commit 8d0fa45

File tree

2 files changed

+200
-21
lines changed

2 files changed

+200
-21
lines changed

contracts/imports/utils.fc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -13,3 +13,6 @@
1313
(slice, int) dict_get?(cell dict, int key_len, slice index)
1414
asm(index dict key_len) "DICTGET" "NULLSWAPIFNOT";
1515

16+
slice dict_get_unwrap(cell dict, int key_len, slice index)
17+
asm(index dict key_len) "DICTGET" "131 THROWIFNOT";
18+

contracts/multitoken_dex.fc

Lines changed: 197 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -1,40 +1,216 @@
1+
#pragma compute-asm-ltr;
2+
3+
#include "./imports/helper.fc";
14
#include "./imports/utils.fc";
25

6+
;; block.tlb
7+
8+
(builder, ()) __store_$message_pfx(builder out, slice destination) inline {
9+
out~store_uint(0x10, 6);
10+
out~store_slice(destination);
11+
return ~store_uint(out, 0, 111);
12+
}
13+
14+
{-
15+
message DexDeploy {
16+
query_id: Int as uint64;
17+
jetton_wallets: map<Address, Address>;
18+
}
19+
20+
message(0x7362d09c) TokenNotification {
21+
query_id: Int as uint64;
22+
amount: Int as coins;
23+
sender: Address;
24+
forward_payload: Slice as remaining;
25+
}
26+
27+
message(0x0f8a7ea5) JettonTransfer {
28+
query_id: Int as uint64;
29+
amount: Int as coins;
30+
destination: Address;
31+
response_destination: Address;
32+
custom_payload: Cell? = null;
33+
forward_ton_amount: Int as coins;
34+
forward_payload: Slice as remaining;
35+
}
36+
-}
37+
38+
const int __op_DexDeploy = 0x0f656aab;
39+
cell __load_DexDeploy(slice in_msg_body) inline {
40+
return in_msg_body.preload_ref();
41+
}
42+
43+
const int __token_op_Swap = 0x4a2663c4;
44+
const int __op_TokenNotification = 0x7362d09c;
45+
(int, int, slice, slice) __load_TokenNotification(slice in_msg_body) inline {
46+
return (in_msg_body~load_uint(64),
47+
in_msg_body~load_coins(),
48+
in_msg_body~load_msg_addr(),
49+
in_msg_body);
50+
}
51+
52+
(builder, ()) __store_JettonTransfer(builder out, (int, int, slice, slice) params) inline {
53+
var (query_id, amount, destination, comment) = params;
54+
out~store_uint(0x0f8a7ea5, 32);
55+
out~store_uint(query_id, 64);
56+
out~store_coins(amount);
57+
out~store_slice(destination);
58+
out~store_slice(destination);
59+
out~store_uint(0, 1);
60+
out~store_coins(1000); ;; 0.000001 - nesting messages is impossible
61+
out~store_uint(0, 33);
62+
return ~store_slice(out, comment);
63+
}
64+
365
{-
4-
global cell jetton_wallets;
5-
global cell assets;
6-
global int assets_sum;
66+
id: Int as uint64; // to create more than one pool
67+
owner_address: Address; // owner is only required for initialization
768

8-
global cell jetton_masters;
69+
tokens_count: Int as uint16;
70+
jetton_wallets: map<Address, Address>; // jetton master -> our wallet
71+
assets: map<Address, Int>; // our jetton wallet -> balance
72+
swap_base: Int as uint128 = 0; // base value for calculating swaps, == `sum(assets)`
973
-}
10-
#include "./imports/data.fc";
1174

12-
#include "./imports/messages.fc";
75+
builder __build_$data(builder out, (int, slice, int, cell, cell, int) params) inline {
76+
var (id, owner_address, tokens_count, jetton_wallets, assets, swap_base) = v;
77+
out~store_uint(id, 64);
78+
out~store_slice(owner_address);
79+
out~store_uint(tokens_count, 16);
80+
out~store_dict(jetton_wallets);
81+
out~store_dict(assets);
82+
return ~store_uint(out, swap_base, 128);
83+
}
84+
85+
(int, (int, slice, int, cell, cell, int)) __load_$data(slice cs) inline {
86+
var id = cs~load_uint(64);
87+
var owner_address = cs~load_msg_addr();
88+
var tokens_count = cs~load_uint(16);
89+
var jetton_wallets = cs~load_dict();
90+
if (jetton_wallets.cell_null?()) {
91+
return (0, (id, owner_address, tokens_count, null(), null(), 0));
92+
}
93+
var assets = cs~load_dict();
94+
var swap_base = cs~load_uint(128);
95+
return (-1, (id, owner_address, tokens_count, jetton_wallets, assets, swap_base));
96+
}
97+
98+
{-
99+
fun transferJettonTo(jetton_wallet: Address, destination: Address, amount: Int, query_id: Int, message: String) {
100+
if (amount > 0) {
101+
send(SendParameters{
102+
to: jetton_wallet,
103+
value: 0,
104+
mode: SendRemainingValue,
105+
body: JettonTransfer{query_id: query_id, amount: amount, destination: destination, response_destination: destination, custom_payload: message.asComment(), forward_ton_amount: self.forward_ton_amount, forward_payload: emptySlice()}.toCell()
106+
});
107+
}
108+
}
109+
-}
110+
111+
() $self.transfer_jetton_to((slice, slice, int, int) params, slice msg) impure inline {
112+
var (jetton_wallet, destination, amount, query_id) = params;
113+
if (amount == 0) {return ();}
114+
115+
send_raw_message(begin_cell()
116+
.__store_$message_pfx(jetton_wallet)
117+
.__store_JettonTransfer((query_id, amount, destination, msg))
118+
.end_cell(), 64);
119+
}
120+
() $self.refund([slice, slice, int, int] params, slice msg) impure inline {
121+
return $self.transfer_jetton_to(untuple4(params), msg);
122+
}
123+
124+
{-
125+
fun calc_swap(had_token_src: Int, had_token_dst: Int, recv_token_src: Int): Int {
126+
// Swap maintains invariant
127+
// (balance1 * balance2 + balance1 * balance3 + ... + balance[n-1] * balance[n]) = const
128+
129+
// It can be proven then that the swap is calculated as following:
130+
return mulDiv(recv_token_src, self.swap_base - had_token_src, self.swap_base + recv_token_src - had_token_dst);
131+
132+
// for two tokens, essentially equivalent to
133+
// return mulDiv(recv_token_src, had_token_dst, had_token_src + recv_token_src);
134+
}
135+
-}
136+
137+
int $self.calc_swap(int had_token_src, int had_token_dst, int recv_token_src, int swap_base) inline {
138+
;; Swap maintains invariant
139+
;; (balance1 * balance2 + balance1 * balance3 + ... + balance[n-1] * balance[n]) = const
140+
141+
;; It can be proven then that the swap is calculated as following:
142+
return muldiv(recv_token_src, swap_base - had_token_src, swap_base + recv_token_src - had_token_dst);
143+
144+
;; for two tokens, essentially equivalent to
145+
;; return muldiv(recv_token_src, had_token_dst, had_token_src + recv_token_src);
146+
}
13147

14148

15149
() recv_internal(int msg_value, cell in_msg, slice in_msg_body) {
16150
slice in_msg_full = in_msg.begin_parse();
17151
(int bounced, slice sender) = in_msg_full~load_bounce_source();
18152
terminate_if(bounced);
19153

20-
int op = in_msg_body~load_uint(32);
154+
var (init, (id, owner, tokens_count, jetton_wallets, assets, swap_base)) = get_data().begin_parse().__load_$data();
21155

22-
;; uninit contract returns `false`
23-
if (~ load_contract()) {
24-
terminate_if(op != op::dex_init);
25-
26-
jetton_wallets = in_msg_body~load_dict();
27-
while (~ jetton_masters.dict_empty?()) {
28-
(_, slice master) = jetton_masters~pop_max(267);
29-
(slice wallet_addr, int ok) = jetton_wallets.dict_get?(267, master);
30-
throw_unless(101, ok);
31-
assets~dict_set(267, wallet_addr, zero_asset);
156+
if (init) {
157+
int op = in_msg_body~load_uint(32);
158+
throw_unless(131, in_msg_body~load_uint(32) == __op_TokenNotification); ;; ignoring incoming NFTs
159+
160+
var (query_id, amount, jetton_sender, op_body) = in_msg_body.__load_TokenNotification();
161+
if (op_body.slice_refs()) { op_body = op_body.preload_ref().begin_parse(); }
162+
163+
var refund = tuple4(sender, jetton_sender, amount, query_id);
164+
165+
(slice old_balance_src, int known_jetton) = assets.dict_get?(257, sender);
166+
ifnot (known_jetton) {
167+
return $self.refund(refund, "Unknown original jetton");
168+
}
169+
if (msg_value <= 400 * 10000000) {
170+
return $self.refund(refund, "Insufficient value to process token");
32171
}
33-
assets_sum = 0;
34172

35-
save_contract();
36-
return ();
173+
;; parsing forward_payload: no Either bit; swap struct either in ref or inline
174+
;; swap#4a2663c4[__token_op_Swap] wanted_master:MsgAddressInt min_expected:(VarUInteger 16) = OpBody;
175+
176+
if ((op_body.slice_bits() < 303) | (op_body~load_uint(32) != __token_op_Swap)) {
177+
return $self.refund(refund, "Unknown operation");
178+
}
179+
180+
(slice other_jw, int known_master) = jetton_wallets.dict_get?(257, op_body~load_msg_addr());
181+
ifnot (known_master) {
182+
return $self.refund(refund, "Unknown jetton master");
183+
}
184+
185+
int old_balance_dst = assets.dict_get_unwrap(257, other_jw).preload_uint(128);
186+
int other_jetton_min_expected = op_body~load_coins();
187+
188+
if (other_jetton_min_expected >= old_balance_dst) {
189+
return $self.refund(refund, "Liquidity pool doesn't have enough funds");
190+
}
191+
;; now, other_jetton_min_expected <= old_balance_dst - 1
192+
193+
int swap_value = min(old_balance_dst - 1,
194+
$self.calc_swap(old_balance_src, old_balance_dst, received, swap_base));
195+
if (other_jetton_min_expected > swap_value) {
196+
return $self.refund(refund, "Slippage protection: swap can't give requested count of tokens");
197+
}
198+
199+
200+
$self.transfer_jetton_to(other_jw, jetton_sender, swap_value, query_id, "Swap completed");
201+
assets~dict_set_builder(257, sender, begin_cell().store_uint(old_balance_src + received, 128));
202+
assets~dict_set_builder(257, other_jw, begin_cell().store_uint(old_balance_dst - swap_value, 128));
203+
swap_base = swap_base + received - swap_value;
204+
} else {
205+
throw_unless(4429, equal_slices(sender, owner));
206+
throw_unless(131, in_msg_body~load_uint(32) == __op_DexDeploy);
207+
208+
jetton_wallets = in_msg_body.__load_DexDeploy();
209+
assets = fill_zeros(jetton_wallets, tokens_count);
210+
211+
send_raw_message(begin_cell().__store_$message_pfx(owner)
212+
.store_uint(0, 32).store_slice("Deployed").end_cell(), 64);
37213
}
38214

39-
215+
begin_cell().__build_$data((id, owner, tokens_count, jetton_wallets, assets, swap_base)).end_cell().set_data();
40216
}

0 commit comments

Comments
 (0)