Source file liquidity_baking_generator.ml
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
open Liquidity_baking_machine
open QCheck2.Gen
let total_xtz = 32_000_000_000_000L
let ten_subsidies = 25_000_000L
let rec remove_last_element = function
| [_] -> []
| x :: rst -> x :: remove_last_element rst
| [] -> raise (Invalid_argument "remove_last_element")
(** Try to shrink a list by removing elements from the tail of said
list.
The elements themselves are not shrinked. *)
let rec shrink_list l =
if l == [] then Seq.empty
else
let l = remove_last_element l in
Seq.cons l (shrink_list l)
let gen_balances : int64 -> int -> int -> balances QCheck2.Gen.t =
fun max_xtz max_tzbtc max_liquidity ->
let open Tezos_test_helpers.Qcheck2_helpers in
let+ xtz = int64_strictly_positive_gen max_xtz
and+ tzbtc = int_strictly_positive_gen max_tzbtc
and+ liquidity = int_strictly_positive_gen max_liquidity in
{xtz; tzbtc; liquidity}
let gen_specs : int -> int -> specs QCheck2.Gen.t =
fun total_tzbtc total_liquidity ->
let* accounts_numbers = int_range 10 20 in
let max_xtz = Int64.(div total_xtz (of_int (50 * accounts_numbers))) in
let max_tzbtc = total_tzbtc / (accounts_numbers + 1) in
let max_liquidity = total_liquidity / accounts_numbers in
let+ cpmm_balance = gen_balances max_xtz max_tzbtc 1
and+ accounts_balances =
list_repeat accounts_numbers (gen_balances max_xtz max_tzbtc max_liquidity)
in
{
cpmm_min_xtz_balance = cpmm_balance.xtz;
cpmm_min_tzbtc_balance = cpmm_balance.tzbtc;
accounts_balances;
}
type 'a optgen = 'a option QCheck2.Gen.t
let ( let*? ) (m : 'a optgen) (f : 'a -> 'b optgen) =
let* x = m in
match x with None -> return None | Some x -> f x
(** [genopt_oneof l] tries to generate a value using the generators of
[l], one at a time.
First, the list [l] is randomized, then each generator is
tried. The first one to return a result (not [None]) is picked. If
all generators returns [None], the generators tries again with the
whole list (at most 100 times). If no generator of [l] is able to
return a result, then [genopt_oneof l] returns [None]. *)
let genopt_oneof (l : 'a optgen list) : 'a optgen =
let* l = QCheck2.Gen.shuffle_l l in
let rec aux n = function
| [] -> if n = 0 then pure None else aux (n - 1) l
| g :: l -> (
let* x = g in
match x with None -> aux n l | Some x -> pure @@ Some x)
in
aux 100 l
let genopt_account ?choice ?(filter = Fun.const true) env : contract_id optgen =
let l =
List.filter
filter
(Option.fold ~none:env.implicit_accounts ~some:(fun x -> [x]) choice)
in
if l = [] then return None else map Option.some (oneofl l)
let genopt_account_with_tzbtc ?choice ?(min = 1) env state =
genopt_account
?choice
~filter:(fun a -> SymbolicMachine.get_tzbtc_balance a env state >= min)
env
let genopt_account_with_xtz ?choice ?(min = 1L) env state =
genopt_account
?choice
~filter:(fun a -> SymbolicMachine.get_xtz_balance a state >= min)
env
let genopt_account_with_liquidity ?choice ?(min = 1) env state =
genopt_account
?choice
~filter:(fun a -> SymbolicMachine.get_liquidity_balance a env state >= min)
env
let genopt_step_tzbtc_to_xtz :
?source:contract_id ->
?destination:contract_id ->
contract_id env ->
SymbolicMachine.t ->
contract_id step optgen =
fun ?source ?destination env state ->
let*? source = genopt_account_with_tzbtc ?choice:source env state in
let*? destination = genopt_account ?choice:destination env in
let+ tzbtc_deposit =
Tezos_test_helpers.Qcheck2_helpers.int_strictly_positive_gen
(SymbolicMachine.get_tzbtc_balance source env state)
in
if
SymbolicMachine.get_tzbtc_balance env.cpmm_contract env state
< Int.max_int - tzbtc_deposit
then Some (SellTzBTC {source; destination; tzbtc_deposit})
else None
let genopt_step_xtz_to_tzbtc :
?source:contract_id ->
?destination:contract_id ->
contract_id env ->
SymbolicMachine.t ->
contract_id step optgen =
fun ?source ?destination env state ->
let*? source = genopt_account_with_xtz ?choice:source env state in
let*? destination = genopt_account ?choice:destination env in
let+ xtz_deposit =
map
Int64.of_int
(int_range
1
(Int64.to_int @@ SymbolicMachine.get_xtz_balance source state))
in
if
SymbolicMachine.get_xtz_balance env.cpmm_contract state
< Int64.(sub max_int (add ten_subsidies xtz_deposit))
then Some (BuyTzBTC {source; destination; xtz_deposit})
else None
let genopt_step_add_liquidity :
?source:contract_id ->
?destination:contract_id ->
contract_id env ->
SymbolicMachine.t ->
contract_id step optgen =
fun ?source ?destination env state ->
let rec find_xtz_deposit candidate max_tzbtc_deposit =
let tzbtc_deposit =
SymbolicMachine.predict_required_tzbtc_deposit candidate env state
in
if tzbtc_deposit <= max_tzbtc_deposit then candidate
else find_xtz_deposit (Int64.div candidate 2L) max_tzbtc_deposit
in
let*? source = genopt_account_with_xtz ?choice:source env state in
let*? destination = genopt_account ?choice:destination env in
let source_xtz_pool = SymbolicMachine.get_xtz_balance source state in
if 1L < source_xtz_pool then
let+ candidate =
Tezos_test_helpers.Qcheck2_helpers.int64_strictly_positive_gen
source_xtz_pool
in
let xtz_deposit =
find_xtz_deposit
candidate
(SymbolicMachine.get_tzbtc_balance source env state)
in
if
SymbolicMachine.get_xtz_balance env.cpmm_contract state
< Int64.(sub max_int (add ten_subsidies xtz_deposit))
then Some (AddLiquidity {source; destination; xtz_deposit})
else None
else pure None
let genopt_step_remove_liquidity :
?source:contract_id ->
?destination:contract_id ->
contract_id env ->
SymbolicMachine.t ->
contract_id step optgen =
fun ?source ?destination env state ->
let*? source = genopt_account_with_liquidity ?choice:source env state in
let*? destination = genopt_account ?choice:destination env in
let lqt_available = SymbolicMachine.get_liquidity_balance source env state in
if 1 < lqt_available then
let+ lqt_burned =
int_range 1 (SymbolicMachine.get_liquidity_balance source env state)
in
Some (RemoveLiquidity {source; destination; lqt_burned})
else return None
let genopt_step :
?source:contract_id ->
?destination:contract_id ->
contract_id env ->
SymbolicMachine.t ->
contract_id step optgen =
fun ?source ?destination env state ->
genopt_oneof
[
genopt_step_tzbtc_to_xtz env state ?source ?destination;
genopt_step_xtz_to_tzbtc env state ?source ?destination;
genopt_step_add_liquidity env state ?source ?destination;
genopt_step_remove_liquidity env state ?source ?destination;
]
let gen_steps :
?source:contract_id ->
?destination:contract_id ->
contract_id env ->
SymbolicMachine.t ->
int ->
contract_id step list QCheck2.Gen.t =
fun ?source ?destination env state size ->
let rec inner env state size random_state =
if size <= 0 then []
else
let h =
QCheck2.Gen.generate1
~rand:random_state
(genopt_step ?source ?destination env state)
in
match h with
| None -> []
| Some h ->
let state = SymbolicMachine.step h env state in
let rst = inner env state (size - 1) random_state in
h :: rst
in
QCheck2.Gen.make_primitive ~gen:(inner env state size) ~shrink:(fun l ->
shrink_list l)
let gen_scenario :
tzbtc -> liquidity -> int -> (specs * contract_id step list) QCheck2.Gen.t =
fun total_tzbtc total_liquidity size ->
let* specs = gen_specs total_tzbtc total_liquidity in
let state, env = SymbolicMachine.build specs in
let+ scenario = gen_steps env state size in
(specs, scenario)
let pp_scenario fmt (specs, steps) =
Format.(
fprintf
fmt
"@[<v>{@ @[<v 2> @[<v 2>specs@ = %a;@]@ @[<v 2>steps@ = @[<v>[ \
%a]@]@]@]}@]"
pp_specs
specs
(pp_print_list
~pp_sep:(fun fmt _ -> fprintf fmt "@ ; ")
(pp_step pp_contract_id))
steps)
let print_scenario = Format.asprintf "%a" pp_scenario
let gen_adversary_scenario :
tzbtc ->
liquidity ->
int ->
(specs * contract_id * contract_id step list) QCheck2.Gen.t =
fun total_tzbtc total_liquidity size ->
let* specs = gen_specs total_tzbtc total_liquidity in
let state, env = SymbolicMachine.build ~subsidy:0L specs in
let* c = oneofl env.implicit_accounts in
let+ scenario = gen_steps ~source:c ~destination:c env state size in
(specs, c, scenario)
let print_adversary_scenario (specs, _, steps) =
Format.asprintf "%a" pp_scenario (specs, steps)