-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathFlowALPModels.cdc
More file actions
2264 lines (1863 loc) · 104 KB
/
FlowALPModels.cdc
File metadata and controls
2264 lines (1863 loc) · 104 KB
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
import "FungibleToken"
import "DeFiActions"
import "DeFiActionsUtils"
import "MOET"
import "Burner"
import "FlowALPMath"
import "FlowALPInterestRates"
import "FlowALPEvents"
access(all) contract FlowALPModels {
/// EImplementation
///
/// Entitlement for internal implementation operations that maintain the pool's state
/// and process asynchronous updates. This entitlement grants access to low-level state
/// management functions used by the protocol's internal mechanisms.
///
/// This entitlement is used internally by the protocol to maintain state consistency
/// and process queued operations. It should not be granted to external users.
access(all) entitlement EImplementation
/// EPosition
///
/// Entitlement for managing positions within the pool.
/// This entitlement grants access to position-specific operations including deposits, withdrawals,
/// rebalancing, and health parameter management for any position in the pool.
///
/// Note that this entitlement provides access to all positions in the pool,
/// not just individual position owners' positions.
access(all) entitlement EPosition
/// ERebalance
///
/// Entitlement for rebalancing positions.
access(all) entitlement ERebalance
/// EGovernance
///
/// Entitlement for governance operations that control pool-wide parameters and configuration.
/// This entitlement grants access to administrative functions that affect the entire pool,
/// including liquidation settings, token support, interest rates, and protocol parameters.
///
/// This entitlement should be granted only to trusted governance entities that manage
/// the protocol's risk parameters and operational settings.
access(all) entitlement EGovernance
/// EParticipant
///
/// Entitlement for general participant operations that allow users to interact with the pool
/// at a basic level. This entitlement grants access to position creation and basic deposit
/// operations without requiring full position ownership.
///
/// This entitlement is more permissive than EPosition and allows anyone to create positions
/// and make deposits, enabling public participation in the protocol while maintaining
/// separation between position creation and position management.
access(all) entitlement EParticipant
/// EPositionAdmin
///
/// Grants access to configure drawdown sinks, top-up sources, and other position settings, for the Position resource.
/// Withdrawal access is provided using FungibleToken.Withdraw.
access(all) entitlement EPositionAdmin
/// BalanceDirection
///
/// The direction of a given balance
access(all) enum BalanceDirection: UInt8 {
/// Denotes that a balance that is withdrawable from the protocol
access(all) case Credit
/// Denotes that a balance that is due to the protocol
access(all) case Debit
}
/// InternalBalance
///
/// A structure used internally to track a position's balance for a particular token
access(all) struct InternalBalance {
/// The current direction of the balance - Credit (owed to borrower) or Debit (owed to protocol)
access(all) var direction: BalanceDirection
/// Internally, position balances are tracked using a "scaled balance".
/// The "scaled balance" is the actual balance divided by the current interest index for the associated token.
/// This means we don't need to update the balance of a position as time passes, even as interest rates change.
/// We only need to update the scaled balance when the user deposits or withdraws funds.
/// The interest index is a number relatively close to 1.0,
/// so the scaled balance will be roughly of the same order of magnitude as the actual balance.
/// We store the scaled balance as UFix128 to align with UFix128 interest indices
/// and to reduce rounding during true ↔ scaled conversions.
access(all) var scaledBalance: UFix128
// Single initializer that can handle both cases
init(
direction: BalanceDirection,
scaledBalance: UFix128
) {
self.direction = direction
self.scaledBalance = scaledBalance
}
/// Records a deposit of the defined amount, updating the inner scaledBalance as well as relevant values
/// in the provided TokenState.
///
/// It's assumed the TokenState and InternalBalance relate to the same token Type,
/// but since neither struct have values defining the associated token,
/// callers should be sure to make the arguments do in fact relate to the same token Type.
///
/// amount is expressed in UFix128 (true token units) to operate in the internal UFix128 domain;
/// public deposit APIs accept UFix64 and are converted at the boundary.
///
access(all) fun recordDeposit(amount: UFix128, tokenState: auth(EImplementation) &{TokenState}) {
switch self.direction {
case BalanceDirection.Credit:
// Depositing into a credit position just increases the balance.
//
// To maximize precision, we could convert the scaled balance to a true balance,
// add the deposit amount, and then convert the result back to a scaled balance.
//
// However, this will only cause problems for very small deposits (fractions of a cent),
// so we save computational cycles by just scaling the deposit amount
// and adding it directly to the scaled balance.
let scaledDeposit = FlowALPMath.trueBalanceToScaledBalance(
amount,
interestIndex: tokenState.getCreditInterestIndex()
)
self.scaledBalance = self.scaledBalance + scaledDeposit
// Increase the total credit balance for the token
tokenState.increaseCreditBalance(by: amount)
case BalanceDirection.Debit:
// When depositing into a debit position, we first need to compute the true balance
// to see if this deposit will flip the position from debit to credit.
let trueBalance = FlowALPMath.scaledBalanceToTrueBalance(
self.scaledBalance,
interestIndex: tokenState.getDebitInterestIndex()
)
// Harmonize comparison with withdrawal: treat an exact match as "does not flip to credit"
if trueBalance >= amount {
// The deposit isn't big enough to clear the debt,
// so we just decrement the debt.
let updatedBalance = trueBalance - amount
self.scaledBalance = FlowALPMath.trueBalanceToScaledBalance(
updatedBalance,
interestIndex: tokenState.getDebitInterestIndex()
)
// Decrease the total debit balance for the token
tokenState.decreaseDebitBalance(by: amount)
} else {
// The deposit is enough to clear the debt,
// so we switch to a credit position.
let updatedBalance = amount - trueBalance
self.direction = BalanceDirection.Credit
self.scaledBalance = FlowALPMath.trueBalanceToScaledBalance(
updatedBalance,
interestIndex: tokenState.getCreditInterestIndex()
)
// Increase the credit balance AND decrease the debit balance
tokenState.increaseCreditBalance(by: updatedBalance)
tokenState.decreaseDebitBalance(by: trueBalance)
}
}
}
/// Records a withdrawal of the defined amount, updating the inner scaledBalance
/// as well as relevant values in the provided TokenState.
///
/// It's assumed the TokenState and InternalBalance relate to the same token Type,
/// but since neither struct have values defining the associated token,
/// callers should be sure to make the arguments do in fact relate to the same token Type.
///
/// amount is expressed in UFix128 for the same rationale as deposits;
/// public withdraw APIs are UFix64 and are converted at the boundary.
///
access(all) fun recordWithdrawal(amount: UFix128, tokenState: auth(EImplementation) &{TokenState}) {
switch self.direction {
case BalanceDirection.Debit:
// Withdrawing from a debit position just increases the debt amount.
//
// To maximize precision, we could convert the scaled balance to a true balance,
// subtract the withdrawal amount, and then convert the result back to a scaled balance.
//
// However, this will only cause problems for very small withdrawals (fractions of a cent),
// so we save computational cycles by just scaling the withdrawal amount
// and subtracting it directly from the scaled balance.
let scaledWithdrawal = FlowALPMath.trueBalanceToScaledBalance(
amount,
interestIndex: tokenState.getDebitInterestIndex()
)
self.scaledBalance = self.scaledBalance + scaledWithdrawal
// Increase the total debit balance for the token
tokenState.increaseDebitBalance(by: amount)
case BalanceDirection.Credit:
// When withdrawing from a credit position,
// we first need to compute the true balance
// to see if this withdrawal will flip the position from credit to debit.
let trueBalance = FlowALPMath.scaledBalanceToTrueBalance(
self.scaledBalance,
interestIndex: tokenState.getCreditInterestIndex()
)
if trueBalance >= amount {
// The withdrawal isn't big enough to push the position into debt,
// so we just decrement the credit balance.
let updatedBalance = trueBalance - amount
self.scaledBalance = FlowALPMath.trueBalanceToScaledBalance(
updatedBalance,
interestIndex: tokenState.getCreditInterestIndex()
)
// Decrease the total credit balance for the token
tokenState.decreaseCreditBalance(by: amount)
} else {
// The withdrawal is enough to push the position into debt,
// so we switch to a debit position.
let updatedBalance = amount - trueBalance
self.direction = BalanceDirection.Debit
self.scaledBalance = FlowALPMath.trueBalanceToScaledBalance(
updatedBalance,
interestIndex: tokenState.getDebitInterestIndex()
)
// Decrease the credit balance AND increase the debit balance
tokenState.decreaseCreditBalance(by: trueBalance)
tokenState.increaseDebitBalance(by: updatedBalance)
}
}
}
}
/// TokenReserveHandler
///
/// Interface for handling token reserve operations. Different token types may require
/// different handling for deposits and withdrawals. For MOET: deposits always burn tokens,
/// withdrawals always mint tokens. For other tokens: standard reserve vault operations.
access(all) struct interface TokenReserveHandler {
/// Returns the token type this handler manages
access(all) view fun getTokenType(): Type
/// Checks if reserves need to exist for this token type
/// For MOET: returns false (MOET uses mint/burn, not reserves)
/// For other tokens: checks if reserves exist in the pool state
access(all) view fun hasReserve(
state: &{PoolState}
): Bool
/// Returns the maximum amount that can be withdrawn given the requested amount
/// For MOET: returns requestedAmount (can mint unlimited)
/// For other tokens: returns min(requestedAmount, reserveBalance) or 0 if no reserves
access(all) fun getMaxWithdrawableAmount(
state: auth(EImplementation) &{PoolState},
requestedAmount: UFix64
): UFix64
/// Deposits tokens
/// For MOET: always burns tokens
/// For other tokens: deposits to reserves
access(all) fun deposit(
state: auth(EImplementation) &{PoolState},
from: @{FungibleToken.Vault}
): UFix64
/// Withdraws tokens
/// For MOET: always mints new tokens
/// For other tokens: withdraws from reserves
access(all) fun withdraw(
state: auth(EImplementation) &{PoolState},
amount: UFix64,
minterRef: &MOET.Minter?
): @{FungibleToken.Vault}
}
/// StandardTokenReserveHandler
///
/// Standard implementation of TokenReserveHandler that interacts with reserve vaults
/// for both deposit and withdraw operations.
access(all) struct StandardTokenReserveHandler: TokenReserveHandler {
access(self) let tokenType: Type
init(tokenType: Type) {
self.tokenType = tokenType
}
access(all) view fun getTokenType(): Type {
return self.tokenType
}
access(all) view fun hasReserve(
state: &{PoolState}
): Bool {
return state.hasReserve(self.tokenType)
}
access(all) fun getMaxWithdrawableAmount(
state: auth(EImplementation) &{PoolState},
requestedAmount: UFix64
): UFix64 {
if let reserveVault = state.borrowReserve(self.tokenType) {
let balance = reserveVault.balance
return requestedAmount > balance ? balance : requestedAmount
}
return 0.0
}
access(all) fun deposit(
state: auth(EImplementation) &{PoolState},
from: @{FungibleToken.Vault}
): UFix64 {
let amount = from.balance
let reserveVault = state.borrowOrCreateReserve(self.tokenType)
reserveVault.deposit(from: <-from)
return amount
}
access(all) fun withdraw(
state: auth(EImplementation) &{PoolState},
amount: UFix64,
minterRef: &MOET.Minter?
): @{FungibleToken.Vault} {
let reserveVault = state.borrowOrCreateReserve(self.tokenType)
return <- reserveVault.withdraw(amount: amount)
}
}
/// MoetTokenReserveHandler
///
/// Special implementation of TokenReserveHandler for MOET tokens.
/// - All deposits BURN tokens (reducing supply, never stored in reserves)
/// - All withdrawals MINT new tokens (increasing supply, never from reserves)
access(all) struct MoetTokenReserveHandler: TokenReserveHandler {
access(all) view fun getTokenType(): Type {
return Type<@MOET.Vault>()
}
access(all) view fun hasReserve(
state: &{PoolState}
): Bool {
// MOET doesn't use reserves (always mints/burns)
return true
}
access(all) fun getMaxWithdrawableAmount(
state: auth(EImplementation) &{PoolState},
requestedAmount: UFix64
): UFix64 {
// MOET can mint unlimited amounts
return requestedAmount
}
access(all) fun deposit(
state: auth(EImplementation) &{PoolState},
from: @{FungibleToken.Vault}
): UFix64 {
// Always burn MOET deposits (never store in reserves)
let amount = from.balance
Burner.burn(<-from)
return amount
}
access(all) fun withdraw(
state: auth(EImplementation) &{PoolState},
amount: UFix64,
minterRef: &MOET.Minter?
): @{FungibleToken.Vault} {
// Always mint MOET withdrawals (never withdraw from reserves)
assert(minterRef != nil, message: "MOET Minter reference required for withdrawal")
return <- minterRef!.mintTokens(amount: amount)
}
}
/// Risk parameters for a token used in effective collateral/debt computations.
/// The collateral and borrow factors are fractional values which represent a discount to the "true/market" value of the token.
/// The size of this discount indicates a subjective assessment of risk for the token.
/// The difference between the effective value and "true" value represents the safety buffer available to prevent loss.
/// - collateralFactor: the factor used to derive effective collateral
/// - borrowFactor: the factor used to derive effective debt
access(all) struct interface RiskParams {
/// The factor (Fc) used to determine effective collateral, in the range [0, 1]
/// See FlowALPMath.effectiveCollateral for additional detail.
access(all) view fun getCollateralFactor(): UFix128
/// The factor (Fd) used to determine effective debt, in the range [0, 1]
/// See FlowALPMath.effectiveDebt for additional detail.
access(all) view fun getBorrowFactor(): UFix128
}
/// RiskParamsImplv1 is the concrete implementation of RiskParams.
access(all) struct RiskParamsImplv1: RiskParams {
/// The factor (Fc) used to determine effective collateral, in the range [0, 1]
/// See FlowALPMath.effectiveCollateral for additional detail.
access(self) let collateralFactor: UFix128
/// The factor (Fd) used to determine effective debt, in the range [0, 1]
/// See FlowALPMath.effectiveDebt for additional detail.
access(self) let borrowFactor: UFix128
init(
collateralFactor: UFix128,
borrowFactor: UFix128,
) {
pre {
collateralFactor <= 1.0: "collateral factor must be <=1"
borrowFactor <= 1.0: "borrow factor must be <=1"
}
self.collateralFactor = collateralFactor
self.borrowFactor = borrowFactor
}
/// Returns the collateral factor (Fc) used to determine effective collateral.
access(all) view fun getCollateralFactor(): UFix128 {
return self.collateralFactor
}
/// Returns the borrow factor (Fd) used to determine effective debt.
access(all) view fun getBorrowFactor(): UFix128 {
return self.borrowFactor
}
}
/// Immutable snapshot of token-level data required for pure math operations
access(all) struct TokenSnapshot {
/// The price of the token denominated in the pool's default token
access(all) let price: UFix128
/// The credit interest index at the time the snapshot was taken
access(all) let creditIndex: UFix128
/// The debit interest index at the time the snapshot was taken
access(all) let debitIndex: UFix128
/// The risk parameters for this token
access(all) let risk: {RiskParams}
init(
price: UFix128,
credit: UFix128,
debit: UFix128,
risk: {RiskParams}
) {
self.price = price
self.creditIndex = credit
self.debitIndex = debit
self.risk = risk
}
/// Returns the price of the token denominated in the pool's default token.
access(all) view fun getPrice(): UFix128 {
return self.price
}
/// Returns the credit interest index at the time the snapshot was taken.
access(all) view fun getCreditIndex(): UFix128 {
return self.creditIndex
}
/// Returns the debit interest index at the time the snapshot was taken.
access(all) view fun getDebitIndex(): UFix128 {
return self.debitIndex
}
/// Returns the risk parameters for this token.
access(all) view fun getRisk(): {RiskParams} {
return self.risk
}
/// Returns the effective debt (denominated in $) for the given debit balance of this snapshot's token.
/// See FlowALPMath.effectiveDebt for additional details.
access(all) view fun effectiveDebt(debitBalance: UFix128): UFix128 {
return FlowALPMath.effectiveDebt(debit: debitBalance, price: self.price, borrowFactor: self.risk.getBorrowFactor())
}
/// Returns the effective collateral (denominated in $) for the given credit balance of this snapshot's token.
/// See FlowALPMath.effectiveCollateral for additional details.
access(all) view fun effectiveCollateral(creditBalance: UFix128): UFix128 {
return FlowALPMath.effectiveCollateral(credit: creditBalance, price: self.price, collateralFactor: self.risk.getCollateralFactor())
}
}
/// Copy-only representation of a position used by pure math (no storage refs)
access(all) struct PositionView {
/// Set of all non-zero balances in the position.
/// If the position does not have a balance for a supported token, no entry for that token exists in this map.
access(all) let balances: {Type: InternalBalance}
/// Set of all token snapshots for which this position has a non-zero balance.
/// If the position does not have a balance for a supported token, no entry for that token exists in this map.
access(all) let snapshots: {Type: TokenSnapshot}
/// The pool's default token type
access(all) let defaultToken: Type
/// The position-specific minimum health threshold for rebalancing eligibility
access(all) let minHealth: UFix128
/// The position-specific maximum health threshold for rebalancing eligibility
access(all) let maxHealth: UFix128
init(
balances: {Type: InternalBalance},
snapshots: {Type: TokenSnapshot},
defaultToken: Type,
min: UFix128,
max: UFix128
) {
self.balances = balances
self.snapshots = snapshots
self.defaultToken = defaultToken
self.minHealth = min
self.maxHealth = max
}
/// Returns the true balance of the given token in this position, accounting for interest.
/// Returns balance 0.0 if the position has no balance stored for the given token.
access(all) view fun trueBalance(ofToken: Type): UFix128 {
if let balance = self.balances[ofToken] {
if let tokenSnapshot = self.snapshots[ofToken] {
switch balance.direction {
case BalanceDirection.Debit:
return FlowALPMath.scaledBalanceToTrueBalance(
balance.scaledBalance, interestIndex: tokenSnapshot.getDebitIndex())
case BalanceDirection.Credit:
return FlowALPMath.scaledBalanceToTrueBalance(
balance.scaledBalance, interestIndex: tokenSnapshot.getCreditIndex())
}
panic("unreachable")
}
}
// If the token doesn't exist in the position, the balance is 0
return 0.0
}
}
/// Computes health = totalEffectiveCollateral / totalEffectiveDebt (∞ when debt == 0)
access(all) view fun healthFactor(view: PositionView): UFix128 {
var effectiveCollateralTotal: UFix128 = 0.0
var effectiveDebtTotal: UFix128 = 0.0
for tokenType in view.balances.keys {
let balance = view.balances[tokenType]!
let snap = view.snapshots[tokenType]!
switch balance.direction {
case BalanceDirection.Credit:
let trueBalance = FlowALPMath.scaledBalanceToTrueBalance(
balance.scaledBalance,
interestIndex: snap.getCreditIndex()
)
effectiveCollateralTotal = effectiveCollateralTotal
+ snap.effectiveCollateral(creditBalance: trueBalance)
case BalanceDirection.Debit:
let trueBalance = FlowALPMath.scaledBalanceToTrueBalance(
balance.scaledBalance,
interestIndex: snap.getDebitIndex()
)
effectiveDebtTotal = effectiveDebtTotal
+ snap.effectiveDebt(debitBalance: trueBalance)
}
}
return FlowALPMath.healthComputation(
effectiveCollateral: effectiveCollateralTotal,
effectiveDebt: effectiveDebtTotal
)
}
/// BalanceSheet
///
/// A struct containing a position's overview in terms of its effective collateral and debt
/// as well as its current health.
access(all) struct BalanceSheet {
/// Effective collateral is a normalized valuation of collateral deposited into this position, denominated in $.
/// In combination with effective debt, this determines how much additional debt can be taken out by this position.
access(all) let effectiveCollateral: UFix128
/// Effective debt is a normalized valuation of debt withdrawn against this position, denominated in $.
/// In combination with effective collateral, this determines how much additional debt can be taken out by this position.
access(all) let effectiveDebt: UFix128
/// The health of the related position
access(all) let health: UFix128
init(
effectiveCollateral: UFix128,
effectiveDebt: UFix128
) {
self.effectiveCollateral = effectiveCollateral
self.effectiveDebt = effectiveDebt
self.health = FlowALPMath.healthComputation(
effectiveCollateral: effectiveCollateral,
effectiveDebt: effectiveDebt
)
}
}
/// View of the pool's pause-related parameters.
access(all) struct PauseParamsView {
/// Whether the pool is currently paused
access(all) let paused: Bool
/// Period (s) following unpause in which liquidations are still not allowed
access(all) let warmupSec: UInt64
/// Timestamp when the pool was most recently unpaused, or nil if never unpaused
access(all) let lastUnpausedAt: UInt64?
init(
paused: Bool,
warmupSec: UInt64,
lastUnpausedAt: UInt64?,
) {
self.paused = paused
self.warmupSec = warmupSec
self.lastUnpausedAt = lastUnpausedAt
}
}
/// View of the pool's global liquidation parameters.
access(all) struct LiquidationParamsView {
/// The health factor a position should be restored to after liquidation
access(all) let targetHF: UFix128
/// The health factor threshold below which a position becomes eligible for liquidation
access(all) let triggerHF: UFix128
init(
targetHF: UFix128,
triggerHF: UFix128,
) {
self.targetHF = targetHF
self.triggerHF = triggerHF
}
}
/// PositionBalance
///
/// A structure returned externally to report a position's balance for a particular token.
/// This structure is NOT used internally.
access(all) struct PositionBalance {
/// The token type for which the balance details relate to
access(all) let vaultType: Type
/// Whether the balance is a Credit or Debit
access(all) let direction: BalanceDirection
/// The balance of the token for the related Position
access(all) let balance: UFix64
init(
vaultType: Type,
direction: BalanceDirection,
balance: UFix64
) {
self.vaultType = vaultType
self.direction = direction
self.balance = balance
}
}
/// PositionDetails
///
/// A structure returned externally to report all of the details associated with a position.
/// This structure is NOT used internally.
access(all) struct PositionDetails {
/// Balance details about each Vault Type deposited to the related Position
access(all) let balances: [PositionBalance]
/// The default token Type of the Pool in which the related position is held
access(all) let poolDefaultToken: Type
/// The available balance of the Pool's default token Type
access(all) let defaultTokenAvailableBalance: UFix64
/// The current health of the related position
access(all) let health: UFix128
init(
balances: [PositionBalance],
poolDefaultToken: Type,
defaultTokenAvailableBalance: UFix64,
health: UFix128
) {
self.balances = balances
self.poolDefaultToken = poolDefaultToken
self.defaultTokenAvailableBalance = defaultTokenAvailableBalance
self.health = health
}
}
/// PoolConfig defines the interface for pool-level configuration parameters.
access(all) struct interface PoolConfig {
// Getters
/// A price oracle that will return the price of each token in terms of the default token.
access(all) view fun getPriceOracle(): {DeFiActions.PriceOracle}
/// Together with borrowFactor, collateralFactor determines borrowing limits for each token.
///
/// When determining the withdrawable loan amount, the value of the token (provided by the PriceOracle)
/// is multiplied by the collateral factor.
///
/// The total "effective collateral" for a position is the value of each token deposited to the position
/// multiplied by its collateral factor.
access(all) view fun getCollateralFactor(tokenType: Type): UFix64
/// Together with collateralFactor, borrowFactor determines borrowing limits for each token.
///
/// The borrowFactor determines how much of a position's "effective collateral" can be borrowed against as a
/// percentage between 0.0 and 1.0
access(all) view fun getBorrowFactor(tokenType: Type): UFix64
/// The count of positions to update per asynchronous update
access(all) view fun getPositionsProcessedPerCallback(): UInt64
/// The target health factor when liquidating a position, which limits how much collateral can be liquidated.
/// After a liquidation, the position's health factor must be less than or equal to this target value.
access(all) view fun getLiquidationTargetHF(): UFix128
/// Period (s) following unpause in which liquidations are still not allowed
access(all) view fun getWarmupSec(): UInt64
/// Time this pool most recently was unpaused
access(all) view fun getLastUnpausedAt(): UInt64?
/// A trusted DEX (or set of DEXes) used by FlowALPv0 as a pricing oracle and trading counterparty for liquidations.
/// The SwapperProvider implementation MUST return a Swapper for all possible (ordered) pairs of supported tokens.
/// If [X1, X2, ..., Xn] is the set of supported tokens, then the SwapperProvider must return a Swapper for all pairs:
/// (Xi, Xj) where i∈[1,n], j∈[1,n], i≠j
///
/// FlowALPv0 does not attempt to construct multi-part paths (using multiple Swappers) or compare prices across Swappers.
/// It relies directly on the Swapper's returned by the configured SwapperProvider.
access(all) view fun getDex(): {DeFiActions.SwapperProvider}
/// Max allowed deviation in basis points between DEX-implied price and oracle price.
access(all) view fun getDexOracleDeviationBps(): UInt16
/// Whether the pool is currently paused
access(all) view fun isPaused(): Bool
/// Enable or disable verbose contract logging for debugging.
access(all) view fun isDebugLogging(): Bool
/// Returns the set of supported token types for this pool
access(all) view fun getSupportedTokens(): [Type]
/// Returns whether the given token type is supported by this pool
access(all) view fun isTokenSupported(tokenType: Type): Bool
/// Gets a swapper from the DEX for the given token pair.
///
/// This function is used during liquidations to compare the liquidator's offer against the DEX price.
/// It expects that a swapper has been configured for every supported collateral-to-debt token pair.
///
/// Panics if:
/// - No swapper is configured for the given token pair (seizeType -> debtType)
///
/// @param seizeType: The collateral token type to swap from
/// @param debtType: The debt token type to swap to
access(all) fun getSwapperForLiquidation(seizeType: Type, debtType: Type): {DeFiActions.Swapper}
// Setters
/// Sets the price oracle. See getPriceOracle for additional details.
/// The oracle's unit of account must match the pool's default token.
access(EImplementation) fun setPriceOracle(_ newOracle: {DeFiActions.PriceOracle}, defaultToken: Type)
/// Sets the collateral factor for a token type. See getCollateralFactor for additional details.
/// Factor must be between 0 and 1.
access(EImplementation) fun setCollateralFactor(tokenType: Type, factor: UFix64)
/// Sets the borrow factor for a token type. See getBorrowFactor for additional details.
/// Factor must be between 0 and 1.
access(EImplementation) fun setBorrowFactor(tokenType: Type, factor: UFix64)
/// Sets the positions processed per callback. See getPositionsProcessedPerCallback for additional details.
access(EImplementation) fun setPositionsProcessedPerCallback(_ count: UInt64)
/// Sets the liquidation target health factor. See getLiquidationTargetHF for additional details.
/// Must be greater than 1.0.
access(EImplementation) fun setLiquidationTargetHF(_ targetHF: UFix128)
/// Sets the warmup period. See getWarmupSec for additional details.
access(EImplementation) fun setWarmupSec(_ warmupSec: UInt64)
/// Sets the last unpaused timestamp. See getLastUnpausedAt for additional details.
access(EImplementation) fun setLastUnpausedAt(_ time: UInt64?)
/// Sets the DEX. See getDex for additional details.
access(EImplementation) fun setDex(_ dex: {DeFiActions.SwapperProvider})
/// Sets the DEX oracle deviation. See getDexOracleDeviationBps for additional details.
access(EImplementation) fun setDexOracleDeviationBps(_ bps: UInt16)
/// Sets the paused state. See isPaused for additional details.
access(EImplementation) fun setPaused(_ paused: Bool)
/// Sets the debug logging state. See isDebugLogging for additional details.
access(EImplementation) fun setDebugLogging(_ enabled: Bool)
}
/// PoolConfigImpl is the concrete implementation of PoolConfig.
access(all) struct PoolConfigImpl: PoolConfig {
/// A price oracle that will return the price of each token in terms of the default token.
access(self) var priceOracle: {DeFiActions.PriceOracle}
/// Together with borrowFactor, collateralFactor determines borrowing limits for each token.
///
/// When determining the withdrawable loan amount, the value of the token (provided by the PriceOracle)
/// is multiplied by the collateral factor.
///
/// The total "effective collateral" for a position is the value of each token deposited to the position
/// multiplied by its collateral factor.
access(self) var collateralFactor: {Type: UFix64}
/// Together with collateralFactor, borrowFactor determines borrowing limits for each token.
///
/// The borrowFactor determines how much of a position's "effective collateral" can be borrowed against as a
/// percentage between 0.0 and 1.0
access(self) var borrowFactor: {Type: UFix64}
/// The count of positions to update per asynchronous update
access(self) var positionsProcessedPerCallback: UInt64
/// The target health factor when liquidating a position, which limits how much collateral can be liquidated.
/// After a liquidation, the position's health factor must be less than or equal to this target value.
access(self) var liquidationTargetHF: UFix128
/// Period (s) following unpause in which liquidations are still not allowed
access(self) var warmupSec: UInt64
/// Time this pool most recently was unpaused
access(self) var lastUnpausedAt: UInt64?
/// A trusted DEX (or set of DEXes) used by FlowALPv0 as a pricing oracle and trading counterparty for liquidations.
/// The SwapperProvider implementation MUST return a Swapper for all possible (ordered) pairs of supported tokens.
/// If [X1, X2, ..., Xn] is the set of supported tokens, then the SwapperProvider must return a Swapper for all pairs:
/// (Xi, Xj) where i∈[1,n], j∈[1,n], i≠j
///
/// FlowALPv0 does not attempt to construct multi-part paths (using multiple Swappers) or compare prices across Swappers.
/// It relies directly on the Swapper's returned by the configured SwapperProvider.
access(self) var dex: {DeFiActions.SwapperProvider}
/// Max allowed deviation in basis points between DEX-implied price and oracle price.
access(self) var dexOracleDeviationBps: UInt16
/// Whether the pool is currently paused
access(self) var paused: Bool
/// Enable or disable verbose contract logging for debugging.
access(self) var debugLogging: Bool
init(
priceOracle: {DeFiActions.PriceOracle},
collateralFactor: {Type: UFix64},
borrowFactor: {Type: UFix64},
positionsProcessedPerCallback: UInt64,
liquidationTargetHF: UFix128,
warmupSec: UInt64,
lastUnpausedAt: UInt64?,
dex: {DeFiActions.SwapperProvider},
dexOracleDeviationBps: UInt16,
paused: Bool,
debugLogging: Bool,
) {
self.priceOracle = priceOracle
self.collateralFactor = collateralFactor
self.borrowFactor = borrowFactor
self.positionsProcessedPerCallback = positionsProcessedPerCallback
self.liquidationTargetHF = liquidationTargetHF
self.warmupSec = warmupSec
self.lastUnpausedAt = lastUnpausedAt
self.dex = dex
self.dexOracleDeviationBps = dexOracleDeviationBps
self.paused = paused
self.debugLogging = debugLogging
}
// Getters
/// Returns the price oracle. See PoolConfig.getPriceOracle.
access(all) view fun getPriceOracle(): {DeFiActions.PriceOracle} {
return self.priceOracle
}
/// Returns the collateral factor for the given token type. See PoolConfig.getCollateralFactor.
access(all) view fun getCollateralFactor(tokenType: Type): UFix64 {
return self.collateralFactor[tokenType]!
}
/// Returns the borrow factor for the given token type. See PoolConfig.getBorrowFactor.
access(all) view fun getBorrowFactor(tokenType: Type): UFix64 {
return self.borrowFactor[tokenType]!
}
/// Returns the count of positions to update per asynchronous update.
access(all) view fun getPositionsProcessedPerCallback(): UInt64 {
return self.positionsProcessedPerCallback
}
/// Returns the target health factor for liquidations. See PoolConfig.getLiquidationTargetHF.
access(all) view fun getLiquidationTargetHF(): UFix128 {
return self.liquidationTargetHF
}
/// Returns the warmup period (s) following unpause during which liquidations are blocked.
access(all) view fun getWarmupSec(): UInt64 {
return self.warmupSec
}
/// Returns the timestamp when the pool was most recently unpaused, or nil if never unpaused.
access(all) view fun getLastUnpausedAt(): UInt64? {
return self.lastUnpausedAt
}
/// Returns the configured DEX SwapperProvider. See PoolConfig.getDex.
access(all) view fun getDex(): {DeFiActions.SwapperProvider} {
return self.dex
}
/// Returns the max allowed deviation in bps between DEX-implied price and oracle price.
access(all) view fun getDexOracleDeviationBps(): UInt16 {
return self.dexOracleDeviationBps
}
/// Returns whether the pool is currently paused.
access(all) view fun isPaused(): Bool {
return self.paused
}
/// Returns whether verbose contract debug logging is enabled.
access(all) view fun isDebugLogging(): Bool {
return self.debugLogging
}
/// Returns the set of supported token types for this pool.
access(all) view fun getSupportedTokens(): [Type] {
return self.collateralFactor.keys
}
/// Returns whether the given token type is supported by this pool.
access(all) view fun isTokenSupported(tokenType: Type): Bool {
return self.collateralFactor[tokenType] != nil
}
/// Gets a swapper from the DEX for the given token pair. See PoolConfig.getSwapperForLiquidation.
access(all) fun getSwapperForLiquidation(seizeType: Type, debtType: Type): {DeFiActions.Swapper} {
return self.dex.getSwapper(inType: seizeType, outType: debtType)
?? panic("No DEX swapper configured for liquidation pair: ".concat(seizeType.identifier).concat(" -> ").concat(debtType.identifier))
}
// Setters
/// Sets the price oracle. See PoolConfig.setPriceOracle.
access(EImplementation) fun setPriceOracle(_ newOracle: {DeFiActions.PriceOracle}, defaultToken: Type) {
pre {
newOracle.unitOfAccount() == defaultToken:
"Price oracle must return prices in terms of the pool's default token"
}
self.priceOracle = newOracle
}
/// Sets the collateral factor for a token type. See PoolConfig.setCollateralFactor.
access(EImplementation) fun setCollateralFactor(tokenType: Type, factor: UFix64) {
pre {
factor > 0.0 && factor <= 1.0:
"Collateral factor must be between 0 and 1"
}
self.collateralFactor[tokenType] = factor
}
/// Sets the borrow factor for a token type. See PoolConfig.setBorrowFactor.
access(EImplementation) fun setBorrowFactor(tokenType: Type, factor: UFix64) {
pre {
factor > 0.0 && factor <= 1.0:
"Borrow factor must be between 0 and 1"
}
self.borrowFactor[tokenType] = factor
}
/// Sets the positions processed per callback. See PoolConfig.setPositionsProcessedPerCallback.
access(EImplementation) fun setPositionsProcessedPerCallback(_ count: UInt64) {
self.positionsProcessedPerCallback = count
}
/// Sets the liquidation target health factor. Must be greater than 1.0.
access(EImplementation) fun setLiquidationTargetHF(_ targetHF: UFix128) {
pre {
targetHF > 1.0:
"targetHF must be > 1.0"
}
self.liquidationTargetHF = targetHF