-
Notifications
You must be signed in to change notification settings - Fork 0
/
Copy pathStructuredPoolsSpec.v
3776 lines (3583 loc) · 181 KB
/
StructuredPoolsSpec.v
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
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
1000
(**
This file contains the Specification and Metaspecification of a Structured Pools Contract.
It is organized as follows:
1. Definitions:
- We begin with some auxiliary definitions.
2. AbstractSpecification:
- We define various propositions which must be true of a contract in order for
it to be a structured pool
- We define a predicate `is_structured_pool` on contracts which contains the
propositions constituting the specification
- Ends with `is_structured_pool` predicate and `is_sp_destruct` tactic
3. MetaSpecification:
- We define and prove six properties which are true of any contract satisfying
the specification. These include:
- Demand Sensitivity
- Nonpathological prices
- Swap Rate Consistency
- Zero-Impact Liquidity Change
- Arbitrage Sensitivity
- Pooled Consistency
- These are designed to justify the correctness and strength of the specification,
hence the name 'metaspecification'
The specification is designed so that it can be imported into a Coq module, e.g. to
verify a contract correct with respect to this specification, or to reason about
structured pools abstractly, possibly in conjunction with other DeFi contracts.
*)
From stdpp Require Import decidable.
From FinCert.Specifications.FA2Spec Require FA2Spec.
From ConCert.Execution Require Import Blockchain.
From ConCert.Execution Require Import BuildUtils.
From ConCert.Execution Require Import Containers.
From ConCert.Execution Require Import ContractCommon.
From ConCert.Execution Require Import Monad.
From ConCert.Execution Require Import ResultMonad.
From ConCert.Execution Require Import Serializable.
From ConCert.Utils Require Import RecordUpdate.
From ConCert.Utils Require Import Extras.
From Coq Require Import FunctionalExtensionality.
From Coq Require Import Ensembles.
From Coq Require Import Permutation.
From Coq Require Import ZArith_base.
From Coq Require Import QArith_base.
From Coq Require Import String.
From Coq Require Import List.
From Coq Require Import Fin.
From Coq.Init Require Import Byte.
From Coq Require Import Lia.
Import ListNotations.
Open Scope N_scope.
Open Scope string.
(* =============================================================================
* Defintions:
We begin with some type and auxiliary type and function definitions which we
use for the contract specification.
The key artefacts here are:
- the token type
- the get_bal function
- the get_rate function
* ============================================================================= *)
Section Definitions.
Context {Base : ChainBase}.
Set Primitive Projections.
Set Nonrecursive Elimination Schemes.
Definition error : Type := N.
Section ErrorCodes.
Definition error_PERMISSIONS_DENIED : error := 1.
Definition error_CONTRACT_NOT_FOUND : error := 2.
Definition error_TOKEN_NOT_FOUND : error := 3.
Definition error_INSUFFICIENT_BALANCE : error := 4.
Definition error_CALL_VIEW_FAILED : error := 5.
Definition error_FAILED_ASSERTION : error := 6.
Definition error_FAILED_DIVISION : error := 7.
Definition error_FAILED_TO_INITIALIZE : error := 8.
End ErrorCodes.
Record token := {
token_address : Address;
token_id : N
}.
(* begin hide *)
Section STDPPInstances.
Global Instance token_eq_dec : base.EqDecision token.
Proof.
unfold EqDecision, Decision.
decide equality.
- apply N.eq_dec.
- apply address_eqdec.
Defined.
Global Program Instance token_countable : countable.Countable token :=
countable.inj_countable' (fun t => (token_address t, token_id t))
(fun x => let (a, i) := x in Build_token a i) _.
Declare Scope token_scope.
Notation "x =? y" := (if token_eq_dec x y then true else false)
(at level 70) : token_scope.
End STDPPInstances.
(* end hide *)
Definition exchange_rate := N.
Record transfer_to := {
to_ : Address;
transfer_token_id : N;
amount : N
}.
Record transfer_data := {
from_ : Address;
txs : list transfer_to
}.
Record mint_data := {
mint_owner : Address;
mint_token_id : N;
qty : N
}.
Definition mint : Type := list mint_data.
(* begin hide *)
Section Serialization.
Global Instance transfer_to_serializable : Serializable transfer_to :=
Derive Serializable transfer_to_rect <Build_transfer_to>.
Global Instance transfer_data_serializable : Serializable transfer_data :=
Derive Serializable transfer_data_rect <Build_transfer_data>.
Global Instance mint_data_serializable : Serializable mint_data :=
Derive Serializable mint_data_rect <Build_mint_data>.
End Serialization.
(* end hide *)
(* a function to get a balance from an FMap *)
Definition get_bal (t : token) (tokens_held : FMap token N) :=
match FMap.find t tokens_held with | Some b => b | None => 0 end.
(* the same function, but named differently for the sake of clarity *)
Definition get_rate (t : token) (rates : FMap token N) : N :=
match FMap.find t rates with | Some r => r | None => 0 end.
End Definitions.
Section AbstractSpecification.
Context {Base : ChainBase}
(* =============================================================================
* The Contract Specification `is_structured_pool` :
We detail a list of propositions of a contract's behavior which must be
proven true of a given contract for it to be a correct structured pool contract.
* ============================================================================= *)
{ Setup Msg State Error : Type }
{ other_entrypoint : Type }
`{Serializable Setup} `{Serializable Msg} `{Serializable State} `{Serializable Error}.
(** Specification of the Msg type:
- A Pool entrypoint, whose interface is defined by the pool_data type
- An Unpool entrypoint, whose interface is defined by the unpool_data type
- A Trade entrypoint, whose interface is defined by the trade_data type
- Possibly other types
*)
Record pool_data := {
token_pooled : token ;
qty_pooled : N ; (* the qty of tokens to be pooled *)
}.
Record unpool_data := {
token_unpooled : token ;
qty_unpooled : N ; (* the qty of pool tokens being turned in *)
}.
Record trade_data := {
token_in_trade : token ;
token_out_trade : token ;
qty_trade : N ; (* the qty of token_in going in *)
}.
(* The Msg typeclass *)
Class Msg_Spec (T : Type) :=
build_msg_spec {
pool : pool_data -> T ;
unpool : unpool_data -> T ;
trade : trade_data -> T ;
(* any other potential entrypoints *)
other : other_entrypoint -> option T ;
}.
(** Specification of the State type:
The contract state keeps track of:
- the exchange rates
- tokens held
- pool token address
- number of outstanding pool tokens
*)
Class State_Spec (T : Type) :=
build_state_spec {
(* the exchange rates *)
stor_rates : T -> FMap token exchange_rate ;
(* token balances *)
stor_tokens_held : T -> FMap token N ;
(* pool token data *)
stor_pool_token : T -> token ;
(* number of outstanding pool tokens *)
stor_outstanding_tokens : T -> N ;
}.
(** Specification of the Setup type:
To initialize the contract, we need:
- the initial rates
- the pool token
*)
Class Setup_Spec (T : Type) :=
build_setup_spec {
init_rates : T -> FMap token exchange_rate ;
init_pool_token : T -> token ;
}.
(* specification of the Error type *)
Class Error_Spec (T : Type) :=
build_error_type {
error_to_Error : error -> T ;
}.
(* we assume that our contract types satisfy the type specifications *)
Context `{Msg_Spec Msg} `{Setup_Spec Setup} `{State_Spec State} `{Error_Spec Error}.
(* First, we assume that all successful calls require a message *)
Definition none_fails (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx,
(* the receive function returns an error if the token to be pooled is not in the
rates map held in the storage (=> is not in the semi-fungible family) *)
exists err : Error,
receive contract chain ctx cstate None = Err err.
(* We also specify that the Msg type is fully characterized by its typeclass *)
Definition msg_destruct (contract : Contract Setup Msg State Error) : Prop :=
forall (m : Msg),
(exists p, m = pool p) \/
(exists u, m = unpool u) \/
(exists t, m = trade t) \/
(exists o, Some m = other o).
(** Specification of the POOL entrypoint *)
(* A successful call to POOL means that token_pooled has an exchange rate (=> is in T) *)
Definition pool_entrypoint_check (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* a successful call *)
receive contract chain ctx cstate (Some (pool (msg_payload))) = Ok(cstate', acts) ->
(* an exchange rate exists *)
exists r_x,
FMap.find msg_payload.(token_pooled) (stor_rates cstate) = Some r_x.
(* When the POOL entrypoint is successfully called, it emits a TRANSFER call to the
token in storage, with q tokens in the payload of the call *)
Definition pool_emits_txns (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (Some (pool (msg_payload))) = Ok(cstate', acts) ->
(* in the acts list there is a transfer call with q tokens as the payload *)
exists transfer_to transfer_data transfer_payload mint_data mint_payload,
(* there is a transfer call *)
let transfer_call := (act_call
(* calls the pooled token address *)
(msg_payload.(token_pooled).(token_address))
(* with amount 0 *)
0
(* and payload transfer_payload *)
(serialize (FA2Spec.Transfer transfer_payload))) in
(* with a transfer in it *)
In transfer_data transfer_payload /\
(* which itself has transfer data *)
In transfer_to transfer_data.(FA2Spec.txs) /\
(* whose quantity is the quantity pooled *)
transfer_to.(FA2Spec.amount) = msg_payload.(qty_pooled) /\
(* there is a mint call in acts *)
let mint_call := (act_call
(* calls the pool token contract *)
(stor_pool_token cstate).(token_address)
(* with amount 0 *)
0
(* and payload mint_payload *)
(serialize (FA2Spec.Mint mint_payload))) in
(* with has mint_data in the payload *)
In mint_data mint_payload /\
(* and the mint data has these properties: *)
let r_x := get_rate msg_payload.(token_pooled) (stor_rates cstate) in
mint_data.(FA2Spec.qty) = msg_payload.(qty_pooled) * r_x /\
mint_data.(FA2Spec.mint_owner) = ctx.(ctx_from) /\
(* these are the only emitted transactions *)
(acts = [ transfer_call ; mint_call ] \/
acts = [ mint_call ; transfer_call ]).
(* When the POOL entrypoint is successfully called, tokens_held goes up appropriately *)
Definition pool_increases_tokens_held (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (Some (pool msg_payload)) = Ok(cstate', acts) ->
(* in cstate', tokens_held has increased at token *)
let token := msg_payload.(token_pooled) in
let qty := msg_payload.(qty_pooled) in
let old_bal := get_bal token (stor_tokens_held cstate) in
let new_bal := get_bal token (stor_tokens_held cstate') in
new_bal = old_bal + qty /\
forall t,
t <> token ->
get_bal t (stor_tokens_held cstate) =
get_bal t (stor_tokens_held cstate').
(* And the rates don't change *)
Definition pool_rates_unchanged (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (Some (pool msg_payload)) = Ok(cstate', acts) ->
(* rates all stay the same *)
forall t,
FMap.find t (stor_rates cstate) = FMap.find t (stor_rates cstate').
(* The outstanding tokens change appropriately *)
Definition pool_outstanding (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (Some (pool msg_payload)) = Ok(cstate', acts) ->
(* rates all stay the same *)
let rate_in := get_rate msg_payload.(token_pooled) (stor_rates cstate) in
let qty := msg_payload.(qty_pooled) in
stor_outstanding_tokens cstate' =
stor_outstanding_tokens cstate + rate_in * qty.
(** Specification of the UNPOOL entrypoint *)
(* We assume an inverse rate function *)
Context { calc_rx_inv : forall (r_x : N) (q : N), N }.
(* A successful call to UNPOOL means that token_pooled has an exchange rate (=> is in T) *)
Definition unpool_entrypoint_check (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* a successful call *)
receive contract chain ctx cstate (Some (unpool (msg_payload))) = Ok(cstate', acts) ->
(* an exchange rate exists *)
exists r_x,
FMap.find msg_payload.(token_unpooled) (stor_rates cstate) = Some r_x.
Definition unpool_entrypoint_check_2 (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* a successful call *)
receive contract chain ctx cstate (Some (unpool (msg_payload))) = Ok(cstate', acts) ->
(* we don't unpool more than we have in reserves *)
qty_unpooled msg_payload <=
get_rate (token_unpooled msg_payload) (stor_rates cstate) * get_bal (token_unpooled msg_payload) (stor_tokens_held cstate).
(* When the UNPOOL entrypoint is successfully called, it emits a BURN call to the
pool_token, with q in the payload *)
Definition unpool_emits_txns (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to UNPOOL was successful *)
receive contract chain ctx cstate (Some (unpool msg_payload)) = Ok(cstate', acts) ->
(* in the acts list there are burn and transfer transactions *)
exists burn_data burn_payload transfer_to transfer_data transfer_payload,
(* there is a burn call in acts *)
let burn_call := (act_call
(* calls the pool token address *)
(stor_pool_token cstate).(token_address)
(* with amount 0 *)
0
(* with payload burn_payload *)
(serialize (FA2Spec.Retire burn_payload))) in
(* with has burn_data in the payload *)
In burn_data burn_payload /\
(* and burn_data has these properties: *)
burn_data.(FA2Spec.retire_amount) = msg_payload.(qty_unpooled) /\
(* the burned tokens go from the unpooler *)
burn_data.(FA2Spec.retiring_party) = ctx.(ctx_from) /\
(* there is a transfer call *)
let transfer_call := (act_call
(* call to the token address *)
(msg_payload.(token_unpooled).(token_address))
(* with amount = 0 *)
0
(* with payload transfer_payload *)
(serialize (FA2Spec.Transfer transfer_payload))) in
(* with a transfer in it *)
In transfer_data transfer_payload /\
(* which itself has transfer data *)
In transfer_to transfer_data.(FA2Spec.txs) /\
(* whose quantity is the quantity pooled *)
let r_x := get_rate msg_payload.(token_unpooled) (stor_rates cstate) in
transfer_to.(FA2Spec.amount) = msg_payload.(qty_unpooled) / r_x /\
(* and these are the only emitted transactions *)
(acts = [ burn_call ; transfer_call ] \/
acts = [ transfer_call ; burn_call ]).
(* When the UNPOOL entrypoint is successfully called, tokens_held goes down appropriately *)
Definition unpool_decreases_tokens_held (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (Some (unpool msg_payload)) = Ok(cstate', acts) ->
(* in cstate', tokens_held has increased at token *)
let token := msg_payload.(token_unpooled) in
let r_x := get_rate token (stor_rates cstate) in
let qty := calc_rx_inv r_x msg_payload.(qty_unpooled) in
let old_bal := get_bal token (stor_tokens_held cstate) in
let new_bal := get_bal token (stor_tokens_held cstate') in
new_bal = old_bal - qty /\
forall t,
t <> token ->
get_bal t (stor_tokens_held cstate) =
get_bal t (stor_tokens_held cstate').
(* When the UNPOOL entrypoint is successfully called, tokens_held goes down appropriately *)
Definition unpool_rates_unchanged (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (Some (unpool msg_payload)) = Ok(cstate', acts) ->
(* rates all stay the same *)
forall t,
FMap.find t (stor_rates cstate) = FMap.find t (stor_rates cstate').
(* Defines how the UNPOOL entrypoint updates outstanding tokens *)
Definition unpool_outstanding (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (Some (unpool msg_payload)) = Ok(cstate', acts) ->
(* rates all stay the same *)
let rate_in := get_rate msg_payload.(token_unpooled) (stor_rates cstate) in
let qty := msg_payload.(qty_unpooled) in
stor_outstanding_tokens cstate' =
stor_outstanding_tokens cstate - qty.
(* A successful call to TRADE means that token_in_trade and token_out_trade have exchange
rates (=> are in T) *)
Definition trade_entrypoint_check (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* a successful call *)
receive contract chain ctx cstate (Some (trade (msg_payload))) = Ok(cstate', acts) ->
(* exchange rates exist *)
exists y r_x r_y,
((FMap.find msg_payload.(token_out_trade) (stor_tokens_held cstate) = Some y) /\
(FMap.find msg_payload.(token_in_trade) (stor_rates cstate) = Some r_x) /\
(FMap.find msg_payload.(token_out_trade) (stor_rates cstate) = Some r_y)).
(* A successful call to TRADE means that the inputs to the trade calculation are
all positive *)
Definition trade_entrypoint_check_2 (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* a successful call *)
receive contract chain ctx cstate (Some (trade (msg_payload))) = Ok(cstate', acts) ->
(* exchange rates exist *)
exists x r_x r_y k,
((FMap.find msg_payload.(token_in_trade) (stor_tokens_held cstate) = Some x) /\
(FMap.find msg_payload.(token_in_trade) (stor_rates cstate) = Some r_x) /\
(FMap.find msg_payload.(token_out_trade) (stor_rates cstate) = Some r_y) /\
(stor_outstanding_tokens cstate = k) /\
r_x > 0 /\ r_y > 0 /\ x > 0 /\ k > 0).
(* Specification of the TRADE entrypoint *)
(* We assume the existence of two functions *)
Context { calc_delta_y : forall (rate_in : N) (rate_out : N) (qty_trade : N) (k : N) (x : N), N }
{ calc_rx' : forall (rate_in : N) (rate_out : N) (qty_trade : N) (k : N) (x : N), N }.
(* When TRADE is successfully called, the trade is priced using the correct formula
given by calculate_trade. The updated rate is also priced using the formula from
calculate_trade. *)
Definition trade_pricing_formula (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload t_x t_y q cstate' acts,
(* the TRADE entrypoint was called succesfully *)
t_x = msg_payload.(token_in_trade) ->
t_y = msg_payload.(token_out_trade) ->
t_x <> t_y ->
q = msg_payload.(qty_trade) ->
receive contract chain ctx cstate (Some (trade msg_payload)) =
Ok(cstate', acts) ->
(* calculate the diffs delta_x and delta_y *)
let delta_x :=
(get_bal t_x (stor_tokens_held cstate')) - (get_bal t_x (stor_tokens_held cstate)) in
let delta_y :=
(get_bal t_y (stor_tokens_held cstate)) - (get_bal t_y (stor_tokens_held cstate')) in
let rate_in := (get_rate t_x (stor_rates cstate)) in
let rate_out := (get_rate t_y (stor_rates cstate)) in
let k := (stor_outstanding_tokens cstate) in
let x := get_bal t_x (stor_tokens_held cstate) in
(* the diff delta_x and delta_y are correct *)
delta_x = q /\
delta_y = calc_delta_y rate_in rate_out q k x.
Definition trade_update_rates (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the TRADE entrypoint was called succesfully *)
receive contract chain ctx cstate (Some (trade msg_payload)) =
Ok(cstate', acts) ->
let t_x := msg_payload.(token_in_trade) in
let t_y := msg_payload.(token_out_trade) in
t_x <> t_y /\
(* calculate the diffs delta_x and delta_y *)
let rate_in := (get_rate t_x (stor_rates cstate)) in
let rate_out := (get_rate t_y (stor_rates cstate)) in
let q := msg_payload.(qty_trade) in
let k := (stor_outstanding_tokens cstate) in
let x := get_bal t_x (stor_tokens_held cstate) in
(* the new rate of t_x is correct *)
let r_x' := calc_rx' rate_in rate_out q k x in
(stor_rates cstate') =
(FMap.add (token_in_trade msg_payload)
(calc_rx' (get_rate (token_in_trade msg_payload) (stor_rates cstate))
(get_rate (token_out_trade msg_payload) (stor_rates cstate)) (qty_trade msg_payload)
(stor_outstanding_tokens cstate) (get_bal (token_in_trade msg_payload) (stor_tokens_held cstate)))
(stor_rates cstate)).
Definition trade_update_rates_formula (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the TRADE entrypoint was called succesfully *)
receive contract chain ctx cstate (Some (trade msg_payload)) =
Ok(cstate', acts) ->
let t_x := msg_payload.(token_in_trade) in
let t_y := msg_payload.(token_out_trade) in
t_x <> t_y /\
(* calculate the diffs delta_x and delta_y *)
let rate_in := (get_rate t_x (stor_rates cstate)) in
let rate_out := (get_rate t_y (stor_rates cstate)) in
let q := msg_payload.(qty_trade) in
let k := (stor_outstanding_tokens cstate) in
let x := get_bal t_x (stor_tokens_held cstate) in
(* the new rate of t_x is correct *)
let r_x' := calc_rx' rate_in rate_out q k x in
FMap.find t_x (stor_rates cstate') = Some r_x' /\
(forall t, t <> t_x ->
FMap.find t (stor_rates cstate') =
FMap.find t (stor_rates cstate)).
(* When TRADE is successfully called, it emits two TRANSFER actions *)
Definition trade_emits_transfers (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx msg_payload acts,
(* the call to TRADE was successful *)
receive contract chain ctx cstate (Some (trade (msg_payload))) = Ok(cstate', acts) ->
(* the acts list consists of two transfer actions, specified as follows: *)
exists transfer_to_x transfer_data_x transfer_payload_x
transfer_to_y transfer_data_y transfer_payload_y,
(* there is a transfer call for t_x *)
let transfer_tx := (act_call
(* call to the correct token address *)
(msg_payload.(token_in_trade).(token_address))
(* with amount = 0 *)
0
(* and payload transfer_payload_x *)
(serialize (FA2Spec.Transfer transfer_payload_x))) in
(* with a transfer in it *)
In transfer_data_x transfer_payload_x /\
(* which itself has transfer data *)
In transfer_to_x transfer_data_x.(FA2Spec.txs) /\
(* whose quantity is the quantity traded, transferred to the contract *)
transfer_to_x.(FA2Spec.amount) = msg_payload.(qty_trade) /\
transfer_to_x.(FA2Spec.to_) = ctx.(ctx_contract_address) /\
(* there is a transfer call for t_y *)
let transfer_ty := (act_call
(* call to the correct token address *)
(msg_payload.(token_out_trade).(token_address))
(* with amount = 0 *)
0
(* and payload transfer_payload_y *)
(serialize (FA2Spec.Transfer transfer_payload_y))) in
(* with a transfer in it *)
In transfer_data_y transfer_payload_y /\
(* which itself has transfer data *)
In transfer_to_y transfer_data_y.(FA2Spec.txs) /\
(* whose quantity is the quantity traded, transferred to the contract *)
let t_x := msg_payload.(token_in_trade) in
let t_y := msg_payload.(token_out_trade) in
let rate_in := (get_rate t_x (stor_rates cstate)) in
let rate_out := (get_rate t_y (stor_rates cstate)) in
let k := (stor_outstanding_tokens cstate) in
let x := get_bal t_x (stor_tokens_held cstate) in
let q := msg_payload.(qty_trade) in
transfer_to_y.(FA2Spec.amount) = calc_delta_y rate_in rate_out q k x /\
transfer_to_y.(FA2Spec.to_) = ctx.(ctx_from) /\
(* acts is only these two transfers *)
(acts = [ transfer_tx ; transfer_ty ] \/
acts = [ transfer_ty ; transfer_tx ]).
Definition trade_tokens_held_update (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to TRADE was successful *)
receive contract chain ctx cstate (Some (trade (msg_payload))) = Ok(cstate', acts) ->
(* in the new state *)
let t_x := msg_payload.(token_in_trade) in
let t_y := msg_payload.(token_out_trade) in
let rate_in := (get_rate t_x (stor_rates cstate)) in
let rate_out := (get_rate t_y (stor_rates cstate)) in
let k := (stor_outstanding_tokens cstate) in
let x := get_bal t_x (stor_tokens_held cstate) in
let delta_x := msg_payload.(qty_trade) in
let delta_y := calc_delta_y rate_in rate_out delta_x k x in
let prev_bal_y := get_bal t_y (stor_tokens_held cstate) in
let prev_bal_x := get_bal t_x (stor_tokens_held cstate) in
(* balances update appropriately *)
get_bal t_y (stor_tokens_held cstate') = (prev_bal_y - delta_y) /\
get_bal t_x (stor_tokens_held cstate') = (prev_bal_x + delta_x) /\
forall t_z,
t_z <> t_x ->
t_z <> t_y ->
get_bal t_z (stor_tokens_held cstate') =
get_bal t_z (stor_tokens_held cstate).
Definition trade_outstanding_update (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to TRADE was successful *)
receive contract chain ctx cstate (Some (trade (msg_payload))) = Ok(cstate', acts) ->
(* in the new state *)
(stor_outstanding_tokens cstate') = (stor_outstanding_tokens cstate).
Definition trade_pricing (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to TRADE was successful *)
receive contract chain ctx cstate (Some (trade (msg_payload))) = Ok(cstate', acts) ->
(* balances for t_x change appropriately *)
FMap.find (token_in_trade msg_payload) (stor_tokens_held cstate') =
Some (get_bal (token_in_trade msg_payload) (stor_tokens_held cstate) + (qty_trade msg_payload)) /\
(* balances for t_y change appropriately *)
let t_x := token_in_trade msg_payload in
let t_y := token_out_trade msg_payload in
let delta_x := qty_trade msg_payload in
let rate_in := (get_rate t_x (stor_rates cstate)) in
let rate_out := (get_rate t_y (stor_rates cstate)) in
let k := (stor_outstanding_tokens cstate) in
let x := get_bal t_x (stor_tokens_held cstate) in
(* in the new state *)
FMap.find (token_out_trade msg_payload) (stor_tokens_held cstate') =
Some (get_bal (token_out_trade msg_payload) (stor_tokens_held cstate)
- (calc_delta_y rate_in rate_out delta_x k x)).
Definition trade_amounts_nonnegative (contract : Contract Setup Msg State Error) : Prop :=
forall cstate chain ctx msg_payload cstate' acts,
(* the call to TRADE was successful *)
receive contract chain ctx cstate (Some (trade (msg_payload))) = Ok(cstate', acts) ->
(* delta_x and delta_y are nonnegative *)
let t_x := msg_payload.(token_in_trade) in
let t_y := msg_payload.(token_out_trade) in
let rate_in := (get_rate t_x (stor_rates cstate)) in
let rate_out := (get_rate t_y (stor_rates cstate)) in
let k := (stor_outstanding_tokens cstate) in
let x := get_bal t_x (stor_tokens_held cstate) in
let delta_x := msg_payload.(qty_trade) in
let delta_y := calc_delta_y rate_in rate_out delta_x k x in
0 <= delta_x /\
0 <= delta_y.
(* Specification of all other entrypoints *)
Definition other_rates_unchanged (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx o acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (other o) = Ok(cstate', acts) ->
(* rates all stay the same *)
forall t,
FMap.find t (stor_rates cstate) = FMap.find t (stor_rates cstate').
Definition other_balances_unchanged (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx o acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (other o) = Ok(cstate', acts) ->
(* balances all stay the same *)
forall t,
FMap.find t (stor_tokens_held cstate) = FMap.find t (stor_tokens_held cstate').
Definition other_outstanding_unchanged (contract : Contract Setup Msg State Error) : Prop :=
forall cstate cstate' chain ctx o acts,
(* the call to POOL was successful *)
receive contract chain ctx cstate (other o) = Ok(cstate', acts) ->
(* balances all stay the same *)
(stor_outstanding_tokens cstate) = (stor_outstanding_tokens cstate').
(** Specification of the functions calc_rx' and calc_delta_y
- This specification distils the features of trading along a convex curve which are
relevant to the correct functioning of structured pools.
- While these are supposed to be nontrivial functions that simulate trading along
a convex curve, `calc_rx'`, `calc_delta_y`, and `calc_rx_inv` can be made trivial
by setting all rates to 1, setting the first function to the identity function,
the second to multiplication by r_x = 1, and the third to division by r_x = 1
*)
(* update_rate function returns positive number if the num, denom are positive *)
Definition update_rate_stays_positive :=
forall r_x r_y delta_x k x,
let r_x' := calc_rx' r_x r_y delta_x k x in
r_x > 0 ->
r_x' > 0.
(* r_x' <= r_x *)
Definition rate_decrease :=
forall r_x r_y delta_x k x,
let r_x' := calc_rx' r_x r_y delta_x k x in
r_x' <= r_x.
(* the inverse rate function is a right inverse of r_x *)
Definition rates_balance :=
forall q t rates prev_state,
let r_x := get_rate t rates in
let x := get_bal t (stor_tokens_held prev_state) in
r_x * calc_rx_inv r_x q = q.
Definition rates_balance_2 :=
forall t prev_state,
let r_x' := calc_rx' (get_rate (token_in_trade t) (stor_rates prev_state))
(get_rate (token_out_trade t) (stor_rates prev_state)) (qty_trade t) (stor_outstanding_tokens prev_state)
(get_bal (token_in_trade t) (stor_tokens_held prev_state)) in
let delta_y := calc_delta_y (get_rate (token_in_trade t) (stor_rates prev_state))
(get_rate (token_out_trade t) (stor_rates prev_state)) (qty_trade t) (stor_outstanding_tokens prev_state)
(get_bal (token_in_trade t) (stor_tokens_held prev_state)) in
let r_x := get_rate (token_in_trade t) (stor_rates prev_state) in
let x := get_bal (token_in_trade t) (stor_tokens_held prev_state) in
let y := get_bal (token_out_trade t) (stor_tokens_held prev_state) in
let r_y:= get_rate (token_out_trade t) (stor_rates prev_state) in
r_x' * (x + qty_trade t) + r_y * (y - delta_y) =
r_x * x + r_y * y.
(* the trade always does slightly worse than predicted *)
Definition trade_slippage :=
forall r_x r_y delta_x k x,
let delta_y := calc_delta_y r_x r_y delta_x k x in
r_y * delta_y <= r_x * delta_x.
Definition trade_slippage_2 :=
forall r_x r_y delta_x k x,
let delta_y := calc_delta_y r_x r_y delta_x k x in
let r_x' := calc_rx' r_x r_y delta_x k x in
r_y * delta_y <= r_x' * delta_x.
(* rates have no positive lower bound *)
Definition arbitrage_lt :=
forall rate_x rate_y ext k x,
0 < ext ->
ext < rate_x ->
exists delta_x,
calc_rx' rate_x rate_y delta_x k x <= ext.
(* calc_delta_y has no positive upper bound *)
Definition arbitrage_gt :=
forall rate_x rate_y ext_goal k x,
rate_x > 0 /\
rate_y > 0 /\
x > 0 /\
k > 0 ->
exists delta_x,
ext_goal <= calc_delta_y rate_x rate_y delta_x k x.
(* Initialization specification *)
Definition initialized_with_positive_rates (contract : Contract Setup Msg State Error) :=
forall chain ctx setup cstate,
(* If the contract initializes successfully *)
init contract chain ctx setup = Ok cstate ->
(* then all rates are nonzero *)
forall t r,
FMap.find t (stor_rates cstate) = Some r ->
r > 0.
Definition initialized_with_zero_balance (contract : Contract Setup Msg State Error) :=
forall chain ctx setup cstate,
(* If the contract initializes successfully *)
init contract chain ctx setup = Ok cstate ->
(* then all token balances initialize to zero *)
forall t,
get_bal t (stor_tokens_held cstate) = 0.
Definition initialized_with_zero_outstanding (contract : Contract Setup Msg State Error) :=
forall chain ctx setup cstate,
(* If the contract initializes successfully *)
init contract chain ctx setup = Ok cstate ->
(* then there are no outstanding tokens *)
stor_outstanding_tokens cstate = 0.
Definition initialized_with_init_rates (contract : Contract Setup Msg State Error) :=
forall chain ctx setup cstate,
(* If the contract initializes successfully *)
init contract chain ctx setup = Ok cstate ->
(* then the init rates map is the same as given in the setup *)
(stor_rates cstate) = (init_rates setup).
Definition initialized_with_pool_token (contract : Contract Setup Msg State Error) :=
forall chain ctx setup cstate,
(* If the contract initializes successfully *)
init contract chain ctx setup = Ok cstate ->
(* then the pool token is the same as given in the setup *)
(stor_pool_token cstate) = (init_pool_token setup).
(** We amalgamate each proposition in the specification into a single proposition,
to form the structured pools predicate on contracts *)
Definition is_structured_pool
(C : Contract Setup Msg State Error) : Prop :=
none_fails C /\
msg_destruct C /\
(* pool entrypoint specification *)
pool_entrypoint_check C /\
pool_emits_txns C /\
pool_increases_tokens_held C /\
pool_rates_unchanged C /\
pool_outstanding C /\
(* unpool entrypoint specification *)
unpool_entrypoint_check C /\
unpool_entrypoint_check_2 C /\
unpool_emits_txns C /\
unpool_decreases_tokens_held C /\
unpool_rates_unchanged C /\
unpool_outstanding C /\
(* trade entrypoint specification *)
trade_entrypoint_check C /\
trade_entrypoint_check_2 C /\
trade_pricing_formula C /\
trade_update_rates C /\
trade_update_rates_formula C /\
trade_emits_transfers C /\
trade_tokens_held_update C /\
trade_outstanding_update C /\
trade_pricing C /\
trade_amounts_nonnegative C /\
(* specification of all other entrypoints *)
other_rates_unchanged C /\
other_balances_unchanged C /\
other_outstanding_unchanged C /\
(* specification of calc_rx' and calc_delta_y *)
update_rate_stays_positive /\
rate_decrease /\
rates_balance /\
rates_balance_2 /\
trade_slippage /\
trade_slippage_2 /\
arbitrage_lt /\
arbitrage_gt /\
(* initialization specification *)
initialized_with_positive_rates C /\
initialized_with_zero_balance C /\
initialized_with_zero_outstanding C /\
initialized_with_init_rates C /\
initialized_with_pool_token C.
(* A tactic to destruct is_sp if it's in the context of a proof *)
Ltac is_sp_destruct :=
match goal with
| is_sp : is_structured_pool _ |- _ =>
unfold is_structured_pool in is_sp;
destruct is_sp as [none_fails_pf is_sp'];
destruct is_sp' as [msg_destruct_pf is_sp'];
(* isolate the pool entrypoint specification *)
destruct is_sp' as [pool_entrypoint_check_pf is_sp'];
destruct is_sp' as [pool_emits_txns_pf is_sp'];
destruct is_sp' as [pool_increases_tokens_held_pf is_sp'];
destruct is_sp' as [pool_rates_unchanged_pf is_sp'];
destruct is_sp' as [pool_outstanding_pf is_sp'];
(* isolate the unpool entrypoint specification *)
destruct is_sp' as [unpool_entrypoint_check_pf is_sp'];
destruct is_sp' as [unpool_entrypoint_check_2_pf is_sp'];
destruct is_sp' as [unpool_emits_txns_pf is_sp'];
destruct is_sp' as [unpool_decreases_tokens_held_pf is_sp'];
destruct is_sp' as [unpool_rates_unchanged_pf is_sp'];
destruct is_sp' as [unpool_outstanding_pf is_sp'];
(* isolate the trade entrypoint specification *)
destruct is_sp' as [trade_entrypoint_check_pf is_sp'];
destruct is_sp' as [trade_entrypoint_check_2_pf is_sp'];
destruct is_sp' as [trade_pricing_formula_pf is_sp'];
destruct is_sp' as [trade_update_rates_pf is_sp'];
destruct is_sp' as [trade_update_rates_formula_pf is_sp'];
destruct is_sp' as [trade_emits_transfers_pf is_sp'];
destruct is_sp' as [trade_tokens_held_update_pf is_sp'];
destruct is_sp' as [trade_outstanding_update_pf is_sp'];
destruct is_sp' as [trade_pricing_pf is_sp'];
destruct is_sp' as [trade_amounts_nonnegative_pf is_sp'];
(* isolate the specification of all other entrypoints *)
destruct is_sp' as [other_rates_unchanged_pf is_sp'];
destruct is_sp' as [other_balances_unchanged_pf is_sp'];
destruct is_sp' as [other_outstanding_unchanged_pf is_sp'];
(* isolate the specification of calc_rx' and calc_delta_y *)
destruct is_sp' as [update_rate_stays_positive_pf is_sp'];
destruct is_sp' as [rate_decrease_pf is_sp'];
destruct is_sp' as [rates_balance_pf is_sp'];
destruct is_sp' as [rates_balance_2_pf is_sp'];
destruct is_sp' as [trade_slippage_pf is_sp'];
destruct is_sp' as [trade_slippage_2_pf is_sp'];
destruct is_sp' as [arbitrage_lt_pf is_sp'];
destruct is_sp' as [arbitrage_gt_pf is_sp'];
(* isolate the initialization specification *)
destruct is_sp' as [initialized_with_positive_rates_pf is_sp'];
destruct is_sp' as [initialized_with_zero_balance_pf is_sp'];
destruct is_sp' as [initialized_with_zero_outstanding_pf is_sp'];
destruct is_sp' as [initialized_with_init_rates_pf initialized_with_pool_token_pf]
end.
(* =============================================================================
* The contract Metaspecification:
We reason about a contract which satisfies the properties of the specification
given here, showing that a contract which satisfies the specification also satisfies
the properties here.
* ============================================================================= *)
Context {contract : Contract Setup Msg State Error}
{ is_sp : is_structured_pool contract }.
(** Demand Sensitivity :
A trade for a given token increases its price relative to other constituent tokens,
so that higher relative demand corresponds to a higher relative price.
Likewise, trading one token in for another decreases the first's relative price in
the pool, corresponding to slackened demand. This enforces the classical notion of
supply and demand
We prove that r_x' > r_x and forall t_z, r_z' = r_z.
*)
(* x decreases relative to z as x => x', z => z' :
z - x <= z' - x' *)
Definition rel_decr (x z x' z' : N) :=
((Z.of_N z) - (Z.of_N x) <= (Z.of_N z') - (Z.of_N x'))%Z.
Lemma rel_decr_lem : forall x x' z : N,
x' <= x ->
rel_decr x z x' z.
Proof.
intros * x_leq.
unfold rel_decr.
lia.
Qed.
(* y increases relative to x as y => y', x => x' :
y - x <= y' - x' *)
Definition rel_incr (y x y' x' : N) :=
((Z.of_N y) - (Z.of_N x) <= (Z.of_N y') - (Z.of_N x'))%Z.
Lemma rel_incr_lem : forall x x' y : N,
x' <= x ->
rel_incr y x y x'.
Proof.
intros * x_leq.
unfold rel_incr.
lia.
Qed.
(** Theorem (Demand Sensitivity):
Let t_x and t_y, t_x \neq t_y, be tokens in our family with nonzero pooled liquidity and exchange rates r_x, r_y > 0.
In a trade t_x to t_y, as r_x is updated to r_x', it decereases relative to r_z for all t_z \neq t_x, and r_y strictly increases relative to r_x.
*)
Theorem demand_sensitivity cstate :
(* For all tokens t_x t_y, rates r_x r_y, and quantities x and y, where *)
forall t_x r_x x t_y r_y y,
(* t_x is a token with nonzero pooled liquidity and with rate r_x > 0, and *)
FMap.find t_x (stor_tokens_held cstate) = Some x /\ x > 0 /\
FMap.find t_x (stor_rates cstate) = Some r_x /\ r_x > 0 ->
(* t_y is a token with nonzero pooled liquidity and with rate r_y > 0 *)
FMap.find t_y (stor_tokens_held cstate) = Some y /\ y > 0 /\
FMap.find t_y (stor_rates cstate) = Some r_y /\ r_y > 0 ->
(* In a trade t_x to t_y ... *)
forall chain ctx msg msg_payload acts cstate',
(* i.e.: a successful call to the contract *)
receive contract chain ctx cstate (Some msg) = Ok(cstate', acts) ->
(* which is a trade *)
msg = trade msg_payload ->
(* from t_x to t_y *)
msg_payload.(token_in_trade) = t_x ->
msg_payload.(token_out_trade) = t_y ->
(* with t_x <> t_y *)
t_x <> t_y ->
(* ... as r_x is updated to r_x': ... *)
let r_x' := get_rate t_x (stor_rates cstate') in
(* (1) r_x decreases relative to all rates r_z, for t_z <> t_x, and *)
(forall t_z,
t_z <> t_x ->
let r_z := get_rate t_z (stor_rates cstate) in
let r_z' := get_rate t_z (stor_rates cstate') in
rel_decr r_x r_z r_x' r_z') /\
(* (2) r_y strictly increases relative to r_x *)
let t_y := msg_payload.(token_out_trade) in
let r_y := get_rate t_y (stor_rates cstate) in
let r_y' := get_rate t_y (stor_rates cstate') in
rel_incr r_y r_x r_y' r_x'.
Proof.
intros ? ? ? ? ? ? t_x_data t_y_data ? ? ? ? ? ? successful_txn msg_is_trade