/
main.sw
1938 lines (1716 loc) · 76.3 KB
/
main.sw
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
contract;
use libraries::{
imports::*,
Quid
};
use std::{
u128::U128,
hash::Hash,
auth::msg_sender,
block::timestamp,
call_frames::{
msg_asset_id,
contract_id,
},
context::{
msg_amount,
this_balance,
},
constants::{
BASE_ASSET_ID,
ZERO_B256
},
token::{
transfer,
mint
},
storage::storage_vec::*,
};
use signed_integers::i64::I64;
use fixed_point::ufp128::UFP128;
use fixed_point::ifp256::IFP256;
storage { // live deeply, brooder
pledges: StorageMap<Address, Pledge> = StorageMap {},
addresses: StorageVec<Address> = StorageVec {}, // store all pledge addresses
votes: StorageMap<Address, (u64, u64)> = StorageMap {}, // short then long
stats: PledgeStats = PledgeStats { // global stats
long: Stats { val_ether: UFP128::zero(),
stress_val: UFP128::zero(), avg_val: UFP128::zero(),
stress_loss: UFP128::zero(), avg_loss: UFP128::zero(),
premiums: UFP128::zero(), rate: UFP128::zero(),
},
short: Stats { val_ether: UFP128::zero(),
stress_val: UFP128::zero(), avg_val: UFP128::zero(),
stress_loss: UFP128::zero(), avg_loss: UFP128::zero(),
premiums: UFP128::zero(), rate: UFP128::zero(),
},
val_ether_sp: UFP128::zero(),
val_total_sp: UFP128::zero(),
/**
long: Stats { val_ether: 0,
stress_val: 0, avg_val: 0,
stress_loss: 0, avg_loss: 0,
premiums: 0, rate: 0,
},
short: Stats { val_ether: 0,
stress_val: 0, avg_val: 0,
stress_loss: 0, avg_loss: 0,
premiums: 0, rate: 0,
},
val_ether_sp: 0,
val_total_sp: 0,
*/
},
live: Pool = Pool { // Active borrower assets
long: Pod { credit: 0, debit: 0, }, // ETH, QD
short: Pod { credit: 0, debit: 0, }, // QD, ETH
// negative balance represents debt after liquidation
// so now original collateral, which was returned after
// liquidation continues to be charged unless the debt
// is relinquished, or sold to a new owner. relinquishing
// can be forced if there's not enough for SP withdrawals
},
deep: Pool = Pool { // Defaulted borrower assets
long: Pod { credit: 0, debit: 0, }, // QD, ETH
short: Pod { credit: 0, debit: 0, }, // ETH, QD
},
// if pay 2x APR make licked pledge debt negative in LP
// kept by pledge until someone offers more for their coll
// negatively charged QD
// external liquidity in to pay for APR.
// they also bear losses first from DP...
brood: Pod = Pod { // Solvency Pool deposits
// TODO this starting value should be 0
// and deposit takes QD that has been
// bridged from ERC20 on L1 to Fuel
// and the same for Ether (gas token)
credit: 0, // QD
debit: 0 // ETH
// long: Pod, { credit: 0, debit: 0 } // QD, ETH in SP
// short: Pod, { credit: 0, debit: 0} // QD, ETH in LP
},
crank: Crank = Crank {
last_update: 0, price: ONE, // eth price in usd / qd per usd
vol: ONE, last_oracle: 0, // timestamp of last oracle update, for assert
// TODO in the future aggregate these into one?
long: Medianizer {
done: true, index: 0, // used for updating pledges
target: 137, // https://science.howstuffworks.com/dictionary/physics-terms/why-is-137-most-magical-number.htm
scale: ONE,
sum_w_k: 0, k: 0,
solvency: UFP128::zero(),
// solvency: 0,
kill_cr: ONE,
},
short: Medianizer {
done: true, index: 0, // used for updating pledges
target: 137, scale: ONE,
sum_w_k: 0, k: 0,
solvency: UFP128::zero(),
// solvency: 0,
kill_cr: ONE
}
},
// TODO
// this should be a vector of vectors
// length is deterministic, can't number of digits of exceed max debt
// index 0 represents 1 significant figure, etc
// the entry at any index is a vector of addresses
// that are sorted by CR.
sorted_longs: StorageVec<Address> = StorageVec {},
long_weights: StorageVec<u64> = StorageVec {},
short_weights: StorageVec<u64> = StorageVec {},
sorted_shorts: StorageVec<Address> = StorageVec {},
}
// TODO is this even necessary? Why simulate at all?
/** This function is shamelessly dangerous
It's essentially a flash loan that simulates
minting QD against a deposit of ETH, selling QD
for more ETH (against internal protocol liquidity)
to re-deposit and grow a leveraged position...
I don't remember if it's hardcoded for 10x leverage
*/
/**
#[storage(read, write)] fn valve(id: Address, short: bool, new_debt_in_qd: u64, _pledge: Pod) -> Pod {
let mut pledge = _pledge;
let mut check_zero = false;
let now_liq_qd: u64 = storage.balances.get(id);
let mut now_coll_in_qd: u64 = 0;
let mut now_debt_in_qd: u64 = 0;
if short {
now_debt_in_qd = ratio(storage.price, pledge.debit, ONE);
now_coll_in_qd = pledge.credit;
} else {
now_coll_in_qd = ratio(storage.price, pledge.credit, ONE);
now_debt_in_qd = pledge.debit;
}
let mut net_val: u64 = now_liq_qd + now_coll_in_qd - now_debt_in_qd;
// (net_val - (1 - 1 / 1.1 = 0.090909...) * col_init) / 11
let mut fee_amt: u64 = (net_val - ratio(DOT_OH_NINE, now_coll_in_qd, ONE)) / 11;
// 11 = 1 + (1 - 1 / 1.1) / fee_%
let mut qd_to_buy: u64 = fee_amt * 110; // (fee_amt / fee_%) i.e div 0.009090909...
let mut end_coll_in_qd: u64 = qd_to_buy + now_coll_in_qd;
let max_debt = ratio(ONE, end_coll_in_qd, MIN_CR);
let mut final_debt: u64 = 0;
if new_debt_in_qd >= max_debt {
final_debt = max_debt;
check_zero = true;
}
else { // max_debt is larger than the requested debt
final_debt = new_debt_in_qd;
end_coll_in_qd = ratio(MIN_CR, final_debt, ONE);
// no need to mint all this QD, gets partially minted in `redeem`, excluding the
qd_to_buy = end_coll_in_qd - now_coll_in_qd; // amount cleared against deep pool's QDebt
fee_amt = ratio(FEE, qd_to_buy, ONE);
}
net_val -= fee_amt;
let eleventh = fee_amt / 11;
let rest = fee_amt - eleventh;
storage.deep.short.debit += rest;
storage.gfund.short.credit += eleventh;
if short {
pledge.credit = end_coll_in_qd;
pledge.debit = ratio(ONE, final_debt, storage.price);
storage.live.short.credit += qd_to_buy;
let eth_to_sell = ratio(ONE, qd_to_buy, storage.price);
// ETH spent on buying QD collateral must be paid back by the borrower to unlock the QD
storage.live.short.debit += eth_to_sell;
// TODO
// we must first redeem QD that we mint out of thin air to purchase the ETH,
// before burning ETH debt with it to purchase QD (undoing the mint) collat
redeem(qd_to_buy);
invert(eth_to_sell);
} else {
// get final collateral value in ETH
let end_coll = ratio(ONE, end_coll_in_qd, storage.price);
pledge.credit = end_coll;
pledge.debit = final_debt;
let delta_coll = end_coll - pledge.credit;
storage.live.long.credit += delta_coll;
// QD spent on buying ETH collateral must be paid back by the borrower to unlock the ETH
storage.live.long.debit += qd_to_buy;
redeem(qd_to_buy);
}
// Liquid ETH value in QD
// = (FinalDebt + Net) * (1 - 1.10 / (Net / FinalDebt + 1))
// Net = liquid QD + initial QD collat - initial ETH debt in QD
let net_div_debt = ratio(ONE, net_val, final_debt) + ONE;
// `between` must >= 0 as a rule
let between = ONE - ratio(ONE, MIN_CR, net_div_debt);
let end_liq_qd = ratio(between,
(final_debt + net_val),
ONE);
assert(!check_zero || end_liq_qd == 0);
if now_liq_qd > end_liq_qd {
let to_burn = now_liq_qd - end_liq_qd;
storage.balances.insert(id,
now_liq_qd - to_burn
);
} else if end_liq_qd > now_liq_qd {
let to_mint = end_liq_qd - now_liq_qd;
storage.balances.insert(id,
now_liq_qd + to_mint
);
}
assert(calc_cr(storage.price, pledge.credit, pledge.debit, short) >= MIN_CR);
return pledge;
}
*/
#[storage(read, write)] fn weights_init(short: bool) {
let mut i = 0;
// let mut n = 125;
if short {
if storage.short_weights.len() != 20 { // only executes once
while i < 21 { // 21 elements total
storage.short_weights.push(0);
// n += 5;
i += 1;
}
}
} else {
if storage.long_weights.len() != 20 { // only executes once
while i < 21 { // 21 elements total
storage.long_weights.push(0);
// n += 5;
i += 1;
}
}
}
// assert(n == 225)
}
/** Weighted Median Algorithm for Solvency Target Voting
* Find value of k in range(1, len(Weights)) such that
* sum(Weights[0:k]) = sum(Weights[k:len(Weights)+1])
* = sum(Weights) / 2
* If there is no such value of k, there must be a value of k
* in the same range range(1, len(Weights)) such that
* sum(Weights[0:k]) > sum(Weights) / 2
*/
#[storage(read, write)] fn rebalance(new_stake: u64, new_vote: u64,
old_stake: u64, old_vote: u64,
short: bool) {
require(new_vote >= 125 && new_vote <= 225
&& new_vote % 5 == 0, VoteError::BadVote);
let mut crank = storage.crank.read();
weights_init(short);
let mut weights = storage.long_weights;
let mut data = crank.long;
if short {
weights = storage.short_weights;
data = crank.short;
}
let mut median = data.target;
let stats = storage.stats.read();
let total = stats.val_total_sp.value.as_u64().unwrap();
let mid_stake = total / 2;
if old_vote != 0 && old_stake != 0 {
let old_index = (old_vote - 125) / 5;
weights.set(old_index,
weights.get(old_index).unwrap().read() - old_stake
);
if old_vote <= median {
data.sum_w_k -= old_stake;
}
}
let index = (new_vote - 125) / 5;
if new_stake != 0 {
weights.set(index,
weights.get(index).unwrap().read() + new_stake
);
}
if new_vote <= median {
data.sum_w_k += new_stake;
}
if total != 0 && mid_stake != 0 {
if median > new_vote {
while data.k >= 1 && (
(data.sum_w_k - weights.get(data.k).unwrap().read()) >= mid_stake
) {
data.sum_w_k -= weights.get(data.k).unwrap().read();
data.k -= 1;
}
} else {
while data.sum_w_k < mid_stake {
data.k += 1;
data.sum_w_k += weights.get(data.k).unwrap().read();
}
}
median = (data.k * 5) + 125; // convert index to target
// TODO can sometimes be a number not divisible by 5, probably fine
if data.sum_w_k == mid_stake {
let intermedian = median + ((data.k + 1) * 5) + 125;
median = intermedian / 2;
}
data.target = median;
} else {
data.sum_w_k = 0;
}
if short {
//storage.short_weights.store_vec(weights);
crank.short = data;
} else {
//storage.long_weights.store_vec(weights);
crank.long = data;
}
storage.crank.write(crank);
}
#[storage(read, write)] fn save_pledge(account: Address, pledge: Pledge, long_touched: bool, short_touched: bool) {
let mut dead_short = false;
let mut dead_long = false;
if short_touched {
// TODO when following TODOs have been solved
// let returned = storage.sorted_shorts.remove(pledge.index_short);
// assert(returned.index_short == pledge.index_short);
// TODO use old values from returned variable and new values from pledge variable
// to call the rebalance() function
if pledge.live.short.debit > 0 && pledge.live.short.credit > 0 {
// TODO helper function
// this function returns a new index for where in the ordering
// this position's reference should be inserted
// let index = helper(pledge_cr);
// TODO insert will move every subsequent element after the position of insertion
// down by one, but this will not update each Pledge's pointer to their index in the vector
// this is a problem because the only way to solve it is to iterate through every position again
// to do something so expensive will make Pledge position updates prohibitively expensive in terms of gas
// storage.sorted_shorts.insert(index, pledge);
} else {
dead_short = true;
}
}
if long_touched { // TODO when above TODOs have been solved
// let returned = storage.sorted_longs.remove(pledge.index_long);
// assert(returned.index_long == pledge.index_long);
// TODO use old values from returned variable and new values from pledge variable
// to call the rebalance() function
if pledge.live.long.debit > 0 && pledge.live.long.credit > 0 {
// storage.sorted_longs.insert(index, pledge);
} else {
dead_long = true;
}
}
if dead_short && dead_long && (pledge.quid == 0)
&& (pledge.ether == 0) { storage.pledges.remove(account); }
else { storage.pledges.insert(account, pledge); }
}
#[storage(read, write)] fn fetch_pledge(owner: Address, create: bool, sync: bool) -> Pledge {
let key = storage.pledges.get(owner);
let mut pledge = Pledge {
live: Pool {
long: Pod { credit: 0, debit: 0 },
short: Pod { credit: 0, debit: 0 },
},
stats: PledgeStats {
long: Stats { val_ether: UFP128::zero(),
stress_val: UFP128::zero(), avg_val: UFP128::zero(),
stress_loss: UFP128::zero(), avg_loss: UFP128::zero(),
premiums: UFP128::zero(), rate: UFP128::zero(),
},
short: Stats { val_ether: UFP128::zero(),
stress_val: UFP128::zero(), avg_val: UFP128::zero(),
stress_loss: UFP128::zero(), avg_loss: UFP128::zero(),
premiums: UFP128::zero(), rate: UFP128::zero(),
},
val_ether_sp: UFP128::zero(),
val_total_sp: UFP128::zero(),
/**
long: Stats { val_ether: 0,
stress_val: 0, avg_val: 0,
stress_loss: 0, avg_loss: 0,
premiums: 0, rate: 0,
},
short: Stats { val_ether: 0,
stress_val: 0, avg_val: 0,
stress_loss: 0, avg_loss: 0,
premiums: 0, rate: 0,
},
val_ether_sp: 0,
val_total_sp: 0,
*/
}, ether: 0, quid: 0,
// index_long: 0,
// index_short: 0,
};
if key.try_read().is_none() {
if create {
storage.addresses.push(owner);
return pledge;
} else {
revert(44);
}
} else {
pledge = key.read();
let brood = storage.brood.read();
let deep = storage.deep.read();
let mut stats = storage.stats.read();
let crank = storage.crank.read(); // get this object from caller
// pass it along to try_clap to save gas on reads TODO
let mut long_touched = false;
let mut short_touched = false;
// Should it trigger auto-redeem / short save 1.1 CR ?
// short_save from lick
if sync {
let mut cr = calc_cr(crank.price,
pledge.live.long.credit,
pledge.live.long.debit,
false
);
if cr > 0 && cr < ONE {
let nums = try_clap(owner,
Pod {
credit: pledge.quid,
debit: pledge.ether
},
Pod {
credit: pledge.live.long.credit,
debit: pledge.live.long.debit
},
false, crank.price
);
pledge.live.long.credit = nums.1;
pledge.live.long.debit = nums.3;
pledge.ether = nums.0;
pledge.quid = nums.2;
long_touched = true;
}
cr = calc_cr(crank.price,
pledge.live.short.credit,
pledge.live.short.debit,
true
);
if cr > 0 && cr < ONE {
// TODO liquidate short
let nums = try_clap(owner,
Pod {
credit: pledge.quid,
debit: pledge.ether
},
Pod {
credit: pledge.live.short.credit,
debit: pledge.live.short.debit
},
true, crank.price
);
pledge.live.short.credit = nums.1;
pledge.live.short.debit = nums.3;
pledge.quid = nums.0;
pledge.ether = nums.2;
short_touched = true;
}
// take profits from DP based on contribution to solvency
// take losses equivalent to profits...the rest deferred till withdrawal
// first some global stats, for contr. to risk calculations
let val_eth_sp = UFP128::from_uint(brood.debit);
stats.val_ether_sp = val_eth_sp * UFP128::from_uint(crank.price);
stats.val_total_sp = UFP128::from_uint(brood.credit) + stats.val_ether_sp;
// TODO write to stats
/**
if sp_stress(None, false) > UFP128::ZERO // stress the long side of the SolvencyPool
&& sp_stress(None, true) > UFP128::ZERO { // stress the short side of the SolvencyPool
// retrieve the Pledge's pending allocation of fees
// stressed value of SolvencyPool's short side, exclusing given pledge
let s_stress_ins_x = sp_stress(Some(owner), true);
let s_delta = self.stats.short.stress_val - s_stress_ins_x;
let s_pcs = s_delta / stats.short.stress_val; // % contrib. to short solvency
// stressed value of SolvencyPool's long side, exclusing given pledge
let l_stress_ins_x = sp_stress(Some(owner), false);
let l_delta = stats.long.stress_val - l_stress_ins_x;
let l_pcs = l_delta / stats.long.stress_val; // % contrib. to long solvency
if s_pcs > UFP128::ZERO && l_pcs > UFP128::ZERO {
// Calculate DeepPool shares to absorb by this pledge
// TODO attack where net postive DP is drained by repeated
// micro pledge updates...limit updates to occur only once an hour
let mut eth = deep.long.debit * l_pcs;
let mut eth_debt = deep.short.credit * s_pcs;
let mut qd_debt = deep.long.credit * l_pcs;
let mut qd = deep.short.debit * s_pcs;
// TODO update logic to be 1:1 (no net loss)
if eth_debt >= eth
{ // net loss in terms of eth
let mut delta = eth_debt - eth;
deep.long.debit -= eth; // the eth gain has been absorbed
if delta > 0 {
// absorb as much as we can from the pledge
let mut min = delta;
if pledge.eth < delta {
min = pledge.eth;
}
deep.short.credit -= min; // TODO -= delta ?
pledge.eth = pledge.eth - min; // decrement user's recorded SP deposit
brood.debit = brood.debit - min; // decrement deposit from SP
delta -= min;
if delta > 0 {
// TODO
// what if fetch_pledge can't absorb the value of the short debt into the ETH deposit,
// should it simply detract from the quid deposit?
}
}
} else
{ // net gain in terms of eth
deep.short.credit -= eth_debt;
deep.long.debit -= eth;
eth -= eth_debt;
// TODO if pledge has any CR between 100-110, take the smaller one first
// add enough eth collat to long / remove enough eth debt from short
// such that the new CR is > 110, repeat again for larger CR side
// remaining eth goes to SP deposit...
pledge.eth += eth;
brood.debit += eth;
}
if qd_debt >= qd
{ // net loss in terms of QD
let mut delta = qd_debt - qd;
self.dead.short.debit -= qd; // the QD gain has been absorbed
if delta > 0 {
let mut min = delta;
if pledge.quid < delta {
min = pledge.quid;
}
deep.long.credit -= min; // TODO -= delta
pledge.quid -= min;
brood.credit -= min;
delta -= min;
if delta > 0 {
// TODO
// use x_margin to absorb into the borrowing position,
// only remainder after x_margin should go to deferral reserve
}
}
} else
{ // net gain in terms of QD
deep.long.credit -= qd_debt;
deep.short.debit -= qd;
qd -= qd_debt;
// TODO if pledge has any CR between 100-110, take the smaller one first
// let mut min = std::min(pledge.long.debit, qd);
// pledge.long.debit -= min;
// qd -= min;
// remove enough QD debt such that CR > 110
// if short CR between 100-110
// add enough QD to collat such that CR > 110
// remaining QD goes to SP deposit...
pledge.quid = pledge.quid + qd;
brood.credit = brood.credit + qd;
}
}
}
*/
// TODO this will remove the pledge if it finds zeroes
// storage.save_pledge(&id, &mut pledge, long_touched, short_touched);
// storage.pledges.insert(who, pledge); // state is updated before being modified by function call that invoked fetch
}
return pledge;
}
}
#[storage(read, write)] fn stress_short_pledge(owner: Address) {
let mut stats = storage.stats.read();
let mut live = storage.live.read();
let mut deep = storage.deep.read();
let crank = storage.crank.read();
let mut p: Pledge = fetch_pledge(owner, false, false);
require(crank.price > ONE, PriceError::NotInitialized);
require(crank.vol > ONE, PriceError::NotInitialized);
let mut vol = UFP128::from_uint(crank.vol); // get annualized volatility of ETH
let price = UFP128::from_uint(crank.price);
let one = IFP256::from(UFP128::from_uint(ONE));
let mut neg_one = one;
neg_one = neg_one.sign_reverse();
let mut touched = false;
let mut due = 0;
// will be sum of each borrowed crypto amt * its price
// TODO ratio
p.stats.short.val_ether = price * UFP128::from_uint(p.live.short.debit) / UFP128::from_uint(ONE);
let val_ether = p.stats.short.val_ether;
let qd = UFP128::from_uint(p.live.short.credit); // collat
if val_ether > UFP128::zero() { // $ value of Pledge ETH debt
touched = true;
let eth = IFP256::from(val_ether);
// let mut iW = eth; // the amount of this crypto in the user's short debt portfolio, times the crypto's price
// iW /= eth; // 1.0 for now...TODO later each crypto will carry a different weight in the portfolio, i.e. divide each iW by total $val
// let var = (iW * iW) * (vol * vol); // aggregate for all Pledge's crypto debt
// let mut ivol = var.sqrt(); // portfolio volatility of the Pledge's borrowed crypto
let scale = UFP128::from_uint(crank.short.scale);
let QD = IFP256::from(qd);
// $ value of borrowed crypto in upward price shocks of avg & bad magnitudes
// let mut pct = stress(true, vol, true);
// let avg_val = (one + pct) * eth;
let pct = stress(false, vol, true);
let stress_val = (one + pct) * eth;
let mut stress_loss = stress_val - QD; // stressed value
// if stress_val > QD that means
if stress_loss < IFP256::zero() { // TODO this doesn't make sense check cpp
stress_loss = IFP256::zero(); // better if this is zero, if it's not
// that means liquidation (debt value worth > QD collat)
}
// let mut avg_loss = avg_val - QD;
stats.short.stress_loss += stress_loss.underlying;
p.stats.short.stress_loss = stress_loss.underlying;
// stats.short.avg_loss += avg_loss;
// p.stats.short.avg_loss = avg_loss.underlying;
// market determined implied volaility
vol *= scale;
// TODO should scale also be applied before `stress` call above?
let delta = pct + one;
let l_n = IFP256::from(ln(delta.underlying) * scale); // * calibrate
let i_stress = IFP256::exp(l_n) - one; // this might be negative
let mut payoff = eth * (one + i_stress);
if payoff > QD {
payoff -= QD;
} else {
payoff = IFP256::zero();
};
p.stats.short.rate = pricing(payoff.underlying,
scale, eth.underlying,
QD.underlying, vol, true
);
p.stats.short.premiums = p.stats.short.rate * val_ether;
stats.short.premiums += p.stats.short.premiums;
due = (
p.stats.short.premiums / UFP128::from_uint(PERIOD)
).value.as_u64().unwrap();
p.live.short.credit -= due; // the user pays their due by losing a bit of QD collateral
live.short.credit -= due; // reduce QD collateral in the LivePool
// pay SolvencyProviders by reducing how much they're owed to absorb in QD debt
if deep.long.credit > due {
deep.long.credit -= due;
} else { // take the remainder and add it to QD collateral to be absorbed from DeepPool
due -= deep.long.credit;
deep.long.credit = 0;
deep.short.debit += due;
}
}
if touched {
storage.stats.write(stats);
storage.live.write(live);
storage.deep.write(deep);
save_pledge(owner, p, true, false);
}
}
#[storage(read, write)] fn stress_long_pledge(owner: Address) {
let mut stats = storage.stats.read();
let mut live = storage.live.read();
let mut deep = storage.deep.read();
let crank = storage.crank.read();
let mut p: Pledge = fetch_pledge(owner, false, false);
require(crank.price > ONE, PriceError::NotInitialized);
require(crank.vol > ONE, PriceError::NotInitialized);
let mut vol = UFP128::from_uint(crank.vol); // get annualized volatility of ETH
let price = UFP128::from_uint(crank.price);
let one = IFP256::from(UFP128::from_uint(ONE));
let mut neg_one = one;
neg_one = neg_one.sign_reverse();
let mut touched = false;
let mut due = 0;
// TODO ratio
p.stats.long.val_ether = price * UFP128::from_uint(p.live.long.credit) / UFP128::from_uint(ONE); // will be sum of each crypto collateral amt * its price
let val_ether = p.stats.long.val_ether;
let qd = UFP128::from_uint(p.live.long.debit); // debt
if val_ether > UFP128::zero() {
// let mut iW = val_ether; // the amount of this crypto in the user's long collateral portfolio, times the crypto's price
// iW /= val_ether; // 1.0 for now...TODO later each crypto will carry a different weight in the portfolio
// let var = (iW * iW) * (iVvol * iVvol); // aggregate for all Pledge's crypto collateral
// let mut vol = var.sqrt(); // total portfolio volatility of the Pledge's crypto collateral
touched = true;
let scale = UFP128::from_uint(crank.long.scale);
let eth = IFP256::from(val_ether);
let QD = IFP256::from(qd);
// $ value of crypto collateral in downward price shocks of bad & avg magnitudes
// let mut pct = stress(true, vol, false); // TODO assert that pct is within the same precision as one = 100%
// let avg_val = (one - pct) * eth;
let pct = stress(false, vol, false);
// model suggested $ value of collat in high stress
let stress_val = (one - pct) * eth;
// model suggested $ amount of insufficient collat
let mut stress_loss = QD - stress_val;
if stress_loss < IFP256::zero() {
stress_loss = IFP256::zero();
}
// let mut avg_loss = QD - avg_val;
// if avg_loss < IFP256::zero() {
// // TODO raise assertion?
// avg_loss = IFP256::zero();
// }
stats.long.stress_loss += stress_loss.underlying;
p.stats.long.stress_loss = stress_loss.underlying;
// stats.long.avg_loss += avg_loss;
// p.stats.long.avg_loss = avg_loss.underlying;
vol *= scale; // market determined implied volaility
let delta = (neg_one * pct) + one; // using high stress pct
let l_n = IFP256::from(ln(delta.underlying) * scale); // * calibrate
let i_stress = neg_one * (IFP256::exp(l_n) - one);
let mut payoff = eth * (one - i_stress);
if payoff > QD {
payoff = IFP256::zero();
} else {
payoff = IFP256::from(
UFP128::from_uint(p.live.long.debit)
) - payoff;
};
p.stats.long.rate = pricing(payoff.underlying,
scale, val_ether, QD.underlying, vol, false
); // APR
p.stats.long.premiums = p.stats.long.rate * QD.underlying;
stats.long.premiums += p.stats.long.premiums;
due = (
p.stats.short.premiums / UFP128::from_uint(PERIOD)
).value.as_u64().unwrap();
let mut due_in_ether = ratio(ONE, crank.price, due);
// Debit Pledge's long side for duration
// (it's credited with ether on creation)
p.live.long.credit -= due_in_ether;
live.long.credit -= due_in_ether;
// pay SolvencyProviders by reducing how much they're owed to absorb in ether debt
if deep.short.credit > due_in_ether {
deep.short.credit -= due_in_ether;
} else { // take the remainder and add it to ether collateral to be absorbed from DeepPool
due_in_ether -= deep.short.credit;
deep.short.credit = 0;
deep.long.debit += due_in_ether;
}
}
if touched {
storage.stats.write(stats);
storage.live.write(live);
storage.deep.write(deep);
save_pledge(owner, p, true, false);
}
}
impl Quid for Contract
{
// getters just for frontend testing
#[storage(read)] fn get_live() -> Pool { return storage.live.read(); }
#[storage(read)] fn get_deep() -> Pool { return storage.deep.read(); }
#[storage(read)] fn get_brood() -> Pod {
return storage.brood.read();
}
#[storage(read)] fn get_pledge_live(who: Address) -> Pool {
let key = storage.pledges.get(who);
if !(key.try_read().is_none()) {
log(69);
let pledge = key.read();
return pledge.live;
}
return Pool {
long: Pod { credit: 42, debit: 0 },
short: Pod { credit: 0, debit: 0 },
}
}
#[storage(read)] fn get_pledge_brood(who: Address) -> Pod {
let key = storage.pledges.get(who);
if !(key.try_read().is_none()) {
let pledge = key.read();
return Pod {
credit: pledge.quid,
debit: pledge.ether
};
}
return Pod { credit: 0, debit: 0 };
}
#[storage(read, write)] fn set_price(price: u64) {
let mut crank = storage.crank.read();
crank.price = price;
storage.crank.write(crank);
}
#[storage(read)] fn get_price() -> u64 {
let crank = storage.crank.read();
return crank.price;
}
#[payable]
#[storage(read, write)] fn borrow(amount: u64, short: bool) { // amount in QD
let crank = storage.crank.read();
require(crank.long.done && crank.short.done, UpdateError::Deadlock);
let account = get_msg_sender_address_or_panic();
let mut pledge = fetch_pledge(account, true, true);
let deposit = msg_amount();
require(amount > ONE, AssetError::BelowMinimum);
let mut live = storage.live.read();
let mut eth = 0;
let mut qd = 0;
// First take care of attached deposit by appending collat
if msg_asset_id() == AssetId::from(contract_id().into()) { // QD
if !short && deposit > 0 {
eth = redeem(deposit);
}
pledge.live.long.credit += eth;
live.long.credit += eth;
}
else if msg_asset_id() == BASE_ASSET_ID { // ETH
if short {
qd = invert(deposit)
}
pledge.live.short.credit += qd;
live.short.credit += qd;
}
else {
revert(42);
}
if !short {
let new_debt = pledge.live.long.debit + amount;
let cr = calc_cr(crank.price, pledge.live.long.credit, new_debt, false);
if cr >= MIN_CR { // requested amount to borrow is within measure
mint(contract_id().into(), amount);
pledge.live.long.debit = new_debt;
live.long.debit += amount;
}
else { // instead of throwing a "below MIN_CR", try to flash loan
// pledge = valve(account,
// false, new_debt,
// live.long, pledge.live.long
// );
}
}
else {
eth = ratio(ONE, amount, crank.price); // convert QD value to ETH amount
let new_debt = pledge.live.short.debit + eth;
let cr = calc_cr(crank.price, pledge.live.short.credit, new_debt, true);
if cr >= MIN_CR {
pledge.live.short.debit = new_debt; // this is how much crypto must be
// eventually bought back (at a later price) to repay debt
live.short.debit += eth;
}
else {
let new_debt_in_qd = ratio(crank.price, new_debt, ONE);
// pledge = valve(account,
// true, new_debt_in_qd,
// live.short, pledge.live.short
// );
}
}
save_pledge(account, pledge, !short, short);
}
// Close out caller's borrowing position by paying
// off all pledge's own debt with own collateral
#[storage(read, write)] fn fold(short: bool ) {
// TODO take an amount, if amount is zero
// then fold the whole pledge
// otherwise shrink by amount
let mut brood = storage.brood.read();
let crank = storage.crank.read();
require(crank.price > 0, PriceError::NotInitialized);
let sender = get_msg_sender_address_or_panic();
let mut pledge = fetch_pledge(sender, false, true);
if short {
let eth = pledge.live.short.debit;
// this is how much QD collat we are returning to SP from LP
let qd = ratio(crank.price, eth, ONE);
// TODO instead of decrementing from DP
// write how much is being debited against DP
// assert that this never exceeds DP balance
// as such, while payments are being made out
// of DP into SP...maintain invariant that
// when SP withdraws
redeem(qd); // assume this QD comes from collat,
// which we actually burn off in the next function
// TODO
// possible bug ETH can leave SP
// paydown the debt in the pledge
// but that ETH stays in the contract
// and it is no longer available to SP withdrawl?
churn(eth, true, sender); // reduce ETH debt
// send ETH back to SP since it was borrowed from
// there in the first place...but since we redeemed
// the QD that was collat, we were able to clear some
// long liquidatons along the way, destroying debt
// brood.debit += DP went to the garden took a little dip
} else { // TODO all the same logic as above applies in shrink
let eth = ratio(ONE, pledge.live.long.debit, crank.price);
invert(eth); // get extra QD from DP short? if more needed
// reduce CRs of LP positions (never eat into orig collat)
// TODO dont go into deep first go into SP
// if SP wants to withdraw, QD value only
// DP coll goes into SP, debt goes into LP
// verify with borrow function
// priority queue? vulnerability
// where a withdrawal takes everyone's APR
churn(pledge.live.long.debit, false, sender);
}
}
// TODO save current stake as old_stake local var, set new stake
// update totals, rebalance for long if long, short if short
// This should be handled as part of sync in fetch_pledge