Skip to content

Commit 3c992f4

Browse files
nieldeckxPhilip
andauthored
fix: redemption fee underflow (#592)
* fix: redemption fee underflow * fix: tests * fix: tests * fix: arkadiko-vaults-manager-v1-2 * fix: governance proposal script * redemptions v1-2 --------- Co-authored-by: --global <--global> Co-authored-by: Philip <philip@Philips-MacBook-Pro.local>
1 parent afe9017 commit 3c992f4

15 files changed

+1054
-309
lines changed

clarity/Clarinet.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -130,8 +130,8 @@ path = "contracts/vaults-v2/arkadiko-vaults-helpers-v1-1.clar"
130130
clarity_version = 2
131131
epoch = 2.4
132132

133-
[contracts.arkadiko-vaults-manager-v1-1]
134-
path = "contracts/vaults-v2/arkadiko-vaults-manager-v1-1.clar"
133+
[contracts.arkadiko-vaults-manager-v1-2]
134+
path = "contracts/vaults-v2/arkadiko-vaults-manager-v1-2.clar"
135135
clarity_version = 2
136136
epoch = 2.4
137137

clarity/contracts/arkadiko-dao.clar

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -498,11 +498,11 @@
498498
{ name: "vaults-manager" }
499499
{
500500
address: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM,
501-
qualified-name: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.arkadiko-vaults-manager-v1-1
501+
qualified-name: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.arkadiko-vaults-manager-v1-2
502502
}
503503
)
504504
(map-set contracts-data
505-
{ qualified-name: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.arkadiko-vaults-manager-v1-1 }
505+
{ qualified-name: 'ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM.arkadiko-vaults-manager-v1-2 }
506506
{
507507
can-mint: true,
508508
can-burn: true
Lines changed: 271 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,271 @@
1+
;; Vaults Manager
2+
;; External operations on vaults (liquidation & redemption)
3+
;;
4+
5+
(use-trait oracle-trait .arkadiko-oracle-trait-v1.oracle-trait)
6+
(use-trait ft-trait 'SP3FBR2AGK5H9QBDH3EEN6DF8EK8JY7RX8QJ5SVTE.sip-010-trait-ft-standard.sip-010-trait)
7+
(use-trait vaults-tokens-trait .arkadiko-vaults-tokens-trait-v1-1.vaults-tokens-trait)
8+
(use-trait vaults-data-trait .arkadiko-vaults-data-trait-v1-1.vaults-data-trait)
9+
(use-trait vaults-sorted-trait .arkadiko-vaults-sorted-trait-v1-1.vaults-sorted-trait)
10+
(use-trait vaults-pool-active-trait .arkadiko-vaults-pool-active-trait-v1-1.vaults-pool-active-trait)
11+
(use-trait vaults-pool-liq-trait .arkadiko-vaults-pool-liq-trait-v1-1.vaults-pool-liq-trait)
12+
(use-trait vaults-helpers-trait .arkadiko-vaults-helpers-trait-v1-1.vaults-helpers-trait)
13+
14+
;; ---------------------------------------------------------
15+
;; Constants
16+
;; ---------------------------------------------------------
17+
18+
(define-constant ERR_NOT_AUTHORIZED u920401)
19+
(define-constant ERR_WRONG_TRAIT u920402)
20+
(define-constant ERR_SHUTDOWN u920501)
21+
(define-constant ERR_CAN_NOT_LIQUIDATE u920001)
22+
(define-constant ERR_UNKNOWN_TOKEN u920002)
23+
(define-constant ERR_NOT_FIRST_VAULT u920003)
24+
25+
(define-constant STATUS_ACTIVE u101)
26+
(define-constant STATUS_CLOSED_BY_LIQUIDATION u201)
27+
(define-constant STATUS_CLOSED_BY_REDEMPTION u202)
28+
29+
;; ---------------------------------------------------------
30+
;; Variables
31+
;; ---------------------------------------------------------
32+
33+
(define-data-var shutdown-activated bool false)
34+
35+
;; ---------------------------------------------------------
36+
;; Maps
37+
;; ---------------------------------------------------------
38+
39+
(define-map redemption-block-last
40+
{
41+
token: principal
42+
}
43+
{
44+
block-last: uint
45+
}
46+
)
47+
48+
;; ---------------------------------------------------------
49+
;; Getter
50+
;; ---------------------------------------------------------
51+
52+
(define-read-only (get-shutdown-activated)
53+
(var-get shutdown-activated)
54+
)
55+
56+
(define-read-only (get-redemption-block-last (token principal))
57+
(default-to
58+
{
59+
block-last: u0,
60+
}
61+
(map-get? redemption-block-last { token: token })
62+
)
63+
)
64+
65+
;; ---------------------------------------------------------
66+
;; Liquidations
67+
;; ---------------------------------------------------------
68+
69+
;; Liquidate vault
70+
(define-public (liquidate-vault
71+
(vaults-tokens <vaults-tokens-trait>)
72+
(vaults-data <vaults-data-trait>)
73+
(vaults-sorted <vaults-sorted-trait>)
74+
(vaults-pool-active <vaults-pool-active-trait>)
75+
(vaults-pool-liq <vaults-pool-liq-trait>)
76+
(vaults-helpers <vaults-helpers-trait>)
77+
(oracle <oracle-trait>)
78+
(owner principal)
79+
(token <ft-trait>)
80+
)
81+
(let (
82+
(vault (unwrap-panic (contract-call? vaults-data get-vault owner (contract-of token))))
83+
(coll-to-debt (try! (contract-call? vaults-helpers get-collateral-to-debt vaults-tokens vaults-data oracle owner (contract-of token) (get collateral vault) (get debt vault))))
84+
85+
(stability-fee (try! (contract-call? vaults-helpers get-stability-fee vaults-tokens vaults-data owner (contract-of token))))
86+
(new-debt (+ stability-fee (get debt vault)))
87+
88+
(collateral (try! (get-collateral-for-liquidation vaults-tokens oracle (contract-of token) (get collateral vault) new-debt)))
89+
)
90+
(asserts! (is-eq (contract-of vaults-tokens) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-tokens"))) (err ERR_WRONG_TRAIT))
91+
(asserts! (is-eq (contract-of vaults-data) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-data"))) (err ERR_WRONG_TRAIT))
92+
(asserts! (is-eq (contract-of vaults-sorted) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-sorted"))) (err ERR_WRONG_TRAIT))
93+
(asserts! (is-eq (contract-of vaults-pool-active) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-pool-active"))) (err ERR_WRONG_TRAIT))
94+
(asserts! (is-eq (contract-of vaults-pool-liq) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-pool-liq"))) (err ERR_WRONG_TRAIT))
95+
(asserts! (is-eq (contract-of vaults-helpers) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-helpers"))) (err ERR_WRONG_TRAIT))
96+
(asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "oracle"))) (err ERR_WRONG_TRAIT))
97+
(asserts! (not (var-get shutdown-activated)) (err ERR_SHUTDOWN))
98+
(asserts! (not (get valid coll-to-debt)) (err ERR_CAN_NOT_LIQUIDATE))
99+
100+
;; Update vault data
101+
(try! (contract-call? vaults-data set-vault owner (contract-of token) STATUS_CLOSED_BY_LIQUIDATION u0 u0))
102+
(try! (contract-call? vaults-sorted remove owner (contract-of token)))
103+
104+
;; Burn debt, mint stability fee
105+
(try! (as-contract (contract-call? vaults-pool-liq burn-usda new-debt)))
106+
(try! (as-contract (contract-call? .arkadiko-dao mint-token .usda-token stability-fee .arkadiko-vaults-pool-fees-v1-1)))
107+
108+
;; Add rewards to liquidation pool
109+
(try! (as-contract (contract-call? vaults-pool-active withdraw token tx-sender (get collateral-needed collateral))))
110+
(try! (as-contract (contract-call? vaults-pool-liq add-rewards vaults-tokens token (get collateral-needed collateral))))
111+
112+
;; Send leftover back to owner
113+
(if (> (get collateral-left collateral) u0)
114+
(try! (as-contract (contract-call? vaults-pool-active withdraw token owner (get collateral-left collateral))))
115+
false
116+
)
117+
118+
(ok true)
119+
)
120+
)
121+
122+
;; Get collateral amount info to use in liquidation
123+
(define-public (get-collateral-for-liquidation (vaults-tokens <vaults-tokens-trait>) (oracle <oracle-trait>) (token principal) (collateral uint) (debt uint))
124+
(let (
125+
(collateral-info (unwrap! (contract-call? vaults-tokens get-token token) (err ERR_UNKNOWN_TOKEN)))
126+
(collateral-price (try! (contract-call? oracle fetch-price (get token-name collateral-info))))
127+
(collateral-value (/ (* collateral (get last-price collateral-price)) (get decimals collateral-price)))
128+
(collateral-needed (/ (* collateral debt) collateral-value))
129+
(collateral-penalty (/ (* collateral-needed (get liquidation-penalty collateral-info)) u10000))
130+
(collateral-total (+ collateral-needed collateral-penalty))
131+
)
132+
(if (>= collateral-total collateral)
133+
(ok { collateral-needed: collateral, collateral-left: u0 })
134+
(ok { collateral-needed: collateral-total, collateral-left: (- collateral collateral-total) })
135+
)
136+
)
137+
)
138+
139+
;; ---------------------------------------------------------
140+
;; Redemption
141+
;; ---------------------------------------------------------
142+
143+
(define-public (redeem-vault
144+
(vaults-tokens <vaults-tokens-trait>)
145+
(vaults-data <vaults-data-trait>)
146+
(vaults-sorted <vaults-sorted-trait>)
147+
(vaults-pool-active <vaults-pool-active-trait>)
148+
(vaults-helpers <vaults-helpers-trait>)
149+
(oracle <oracle-trait>)
150+
(owner principal)
151+
(token <ft-trait>)
152+
(debt-payoff uint)
153+
(prev-owner-hint (optional principal))
154+
)
155+
(let (
156+
(redeemer tx-sender)
157+
(vault (unwrap-panic (contract-call? vaults-data get-vault owner (contract-of token))))
158+
(token-list (unwrap-panic (contract-call? vaults-sorted get-token (contract-of token))))
159+
160+
(collateral-info (unwrap! (contract-call? vaults-tokens get-token (contract-of token)) (err ERR_UNKNOWN_TOKEN)))
161+
(collateral-price (try! (contract-call? oracle fetch-price (get token-name collateral-info))))
162+
(collateral-value (/ (* (get collateral vault) (get last-price collateral-price)) (get decimals collateral-price)))
163+
164+
(stability-fee (try! (contract-call? vaults-helpers get-stability-fee vaults-tokens vaults-data owner (contract-of token))))
165+
(debt-total (+ (get debt vault) stability-fee))
166+
167+
(debt-payoff-used (if (>= debt-payoff debt-total)
168+
debt-total
169+
debt-payoff
170+
))
171+
(debt-left (if (>= debt-payoff debt-total)
172+
u0
173+
(- debt-total debt-payoff)
174+
))
175+
176+
(fee (try! (get-redemption-fee vaults-tokens (contract-of token))))
177+
(fee-block-last (get block-last (get-redemption-block-last (contract-of token))))
178+
(fee-block-last-cap (if (< fee-block-last (- burn-block-height (get redemption-fee-block-interval collateral-info)))
179+
(- burn-block-height (get redemption-fee-block-interval collateral-info))
180+
fee-block-last
181+
))
182+
(fee-block-change (/ debt-payoff-used (get redemption-fee-block-rate collateral-info)))
183+
184+
(new-redemption-last-block (if (< (+ fee-block-last-cap fee-block-change) burn-block-height)
185+
(+ fee-block-last-cap fee-block-change)
186+
burn-block-height
187+
))
188+
189+
(collateral-needed (/ (* (get collateral vault) debt-payoff-used) collateral-value))
190+
(collateral-fee (/ (* collateral-needed (get current-fee fee)) u10000))
191+
(collateral-received (- collateral-needed collateral-fee))
192+
193+
(collateral-left (- (get collateral vault) collateral-needed))
194+
)
195+
(asserts! (is-eq (contract-of vaults-tokens) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-tokens"))) (err ERR_WRONG_TRAIT))
196+
(asserts! (is-eq (contract-of vaults-data) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-data"))) (err ERR_WRONG_TRAIT))
197+
(asserts! (is-eq (contract-of vaults-sorted) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-sorted"))) (err ERR_WRONG_TRAIT))
198+
(asserts! (is-eq (contract-of vaults-pool-active) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-pool-active"))) (err ERR_WRONG_TRAIT))
199+
(asserts! (is-eq (contract-of vaults-helpers) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "vaults-helpers"))) (err ERR_WRONG_TRAIT))
200+
(asserts! (is-eq (contract-of oracle) (unwrap-panic (contract-call? .arkadiko-dao get-qualified-name-by-name "oracle"))) (err ERR_WRONG_TRAIT))
201+
(asserts! (not (var-get shutdown-activated)) (err ERR_SHUTDOWN))
202+
(asserts! (is-eq owner (unwrap-panic (get first-owner token-list))) (err ERR_NOT_FIRST_VAULT))
203+
204+
(if (is-eq debt-left u0)
205+
;; Vault closed
206+
(begin
207+
(try! (as-contract (contract-call? vaults-pool-active withdraw token owner collateral-left)))
208+
(try! (contract-call? vaults-data set-vault owner (contract-of token) STATUS_CLOSED_BY_REDEMPTION u0 u0))
209+
(try! (contract-call? vaults-sorted remove owner (contract-of token)))
210+
)
211+
212+
;; Partial redemption
213+
(let (
214+
(nicr (/ (* collateral-left u100000000) debt-left))
215+
)
216+
(try! (contract-call? vaults-data set-vault owner (contract-of token) STATUS_ACTIVE collateral-left debt-left))
217+
(try! (contract-call? vaults-sorted reinsert owner (contract-of token) nicr prev-owner-hint))
218+
)
219+
)
220+
221+
;; Burn USDA
222+
(try! (as-contract (contract-call? .arkadiko-dao burn-token .usda-token debt-payoff-used redeemer)))
223+
224+
;; Send tokens to redeemer
225+
(try! (as-contract (contract-call? vaults-pool-active withdraw token redeemer collateral-received)))
226+
227+
;; Get fee
228+
(try! (as-contract (contract-call? vaults-pool-active withdraw token .arkadiko-vaults-pool-fees-v1-1 collateral-fee)))
229+
230+
;; Increase redemption fee by decreasing last block
231+
(map-set redemption-block-last { token: (contract-of token) }
232+
{ block-last: new-redemption-last-block }
233+
)
234+
235+
;; Return
236+
(ok { debt-payoff-used: debt-payoff-used, collateral-received: collateral-received, collateral-fee: collateral-fee })
237+
)
238+
)
239+
240+
;; Get current redemption fee in bps
241+
;; Fee has a min/max and moves between these values
242+
;; Fee goes up on redemption, fee goes down over time
243+
(define-public (get-redemption-fee (vaults-tokens <vaults-tokens-trait>) (token principal))
244+
(let (
245+
(collateral-info (unwrap! (contract-call? vaults-tokens get-token token) (err ERR_UNKNOWN_TOKEN)))
246+
(fee-info (get-redemption-block-last token))
247+
(fee-diff (- (get redemption-fee-max collateral-info) (get redemption-fee-min collateral-info)))
248+
(block-diff (- burn-block-height (get block-last fee-info)))
249+
(fee-change (/ (* fee-diff block-diff) (get redemption-fee-block-interval collateral-info)))
250+
(current-fee (if (>= fee-change fee-diff)
251+
(get redemption-fee-min collateral-info)
252+
(- (get redemption-fee-max collateral-info) fee-change)
253+
))
254+
)
255+
(ok { current-fee: current-fee })
256+
)
257+
)
258+
259+
;; ---------------------------------------------------------
260+
;; Admin
261+
;; ---------------------------------------------------------
262+
263+
(define-public (set-shutdown-activated (activated bool))
264+
(begin
265+
(asserts! (is-eq contract-caller (contract-call? .arkadiko-dao get-dao-owner)) (err ERR_NOT_AUTHORIZED))
266+
267+
(var-set shutdown-activated activated)
268+
269+
(ok true)
270+
)
271+
)

clarity/contracts/wstx-token.clar

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -140,7 +140,7 @@
140140

141141
(begin
142142
(var-set protocol-addresses (list
143-
.arkadiko-vaults-manager-v1-1
143+
.arkadiko-vaults-manager-v1-2
144144
.arkadiko-vaults-operations-v1-3
145145
.arkadiko-vaults-pool-active-v1-1
146146
.arkadiko-vaults-pool-fees-v1-1

clarity/deployments/default.devnet-plan.yaml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -581,10 +581,10 @@ plan:
581581
anchor-block-only: true
582582
clarity-version: 2
583583
- contract-publish:
584-
contract-name: arkadiko-vaults-manager-v1-1
584+
contract-name: arkadiko-vaults-manager-v1-2
585585
expected-sender: ST1PQHQKV0RJXZFY1DGX8MNSNYVE3VGZJSRTPGZGM
586586
cost: 125560
587-
path: contracts/vaults-v2/arkadiko-vaults-manager-v1-1.clar
587+
path: contracts/vaults-v2/arkadiko-vaults-manager-v1-2.clar
588588
anchor-block-only: true
589589
clarity-version: 2
590590
- contract-publish:

0 commit comments

Comments
 (0)