-
Notifications
You must be signed in to change notification settings - Fork 2
Expand file tree
/
Copy pathclose_position_precision_test.cdc
More file actions
286 lines (235 loc) · 11.1 KB
/
Copy pathclose_position_precision_test.cdc
File metadata and controls
286 lines (235 loc) · 11.1 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
import Test
import BlockchainHelpers
import "MOET"
import "FlowALPv0"
import "FlowALPMath"
import "test_helpers.cdc"
// -----------------------------------------------------------------------------
// Close Position Precision Test Suite
//
// Tests close position functionality with focus on:
// 1. Balance increases (collateral appreciation)
// 2. Balance falls (collateral depreciation)
// 3. Rounding precision and shortfall tolerance
// -----------------------------------------------------------------------------
access(all) var snapshot: UInt64 = 0
access(all)
fun setup() {
deployContracts()
snapshot = getCurrentBlockHeight()
}
// =============================================================================
// Test 1: Close position with no debt
// =============================================================================
access(all)
fun test_closePosition_noDebt() {
log("\n=== Test: Close Position with No Debt ===")
// Setup: price = 1.0
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0)
// Create pool & enable token
createAndStorePool(signer: PROTOCOL_ACCOUNT, defaultTokenIdentifier: MOET_TOKEN_IDENTIFIER, beFailed: false)
addSupportedTokenZeroRateCurve(signer: PROTOCOL_ACCOUNT, tokenTypeIdentifier: FLOW_TOKEN_IDENTIFIER, collateralFactor: 0.8, borrowFactor: 1.0, depositRate: 1_000_000.0, depositCapacityCap: 1_000_000.0)
let user = Test.createAccount()
setupMoetVault(user, beFailed: false)
mintFlow(to: user, amount: 1_000.0)
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user)
// Open position with pushToDrawDownSink = false (no debt)
let openRes = _executeTransaction(
"../transactions/flow-alp/position/create_position.cdc",
[100.0, FLOW_VAULT_STORAGE_PATH, false],
user
)
Test.expect(openRes, Test.beSucceeded())
// Verify no MOET was borrowed
let moetBalance = getBalance(address: user.address, vaultPublicPath: MOET.VaultPublicPath)!
Test.assertEqual(0.0, moetBalance)
// Close position (ID 0)
let closeRes = _executeTransaction(
"../transactions/flow-alp/position/repay_and_close_position.cdc",
[UInt64(0)],
user
)
Test.expect(closeRes, Test.beSucceeded())
log("✅ Successfully closed position with no debt")
}
// =============================================================================
// Test 2: Close position with debt
// =============================================================================
access(all)
fun test_closePosition_withDebt() {
log("\n=== Test: Close Position with Debt ===")
// Reset price to 1.0 for this test
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0)
// Reuse existing pool from previous test
let user = Test.createAccount()
setupMoetVault(user, beFailed: false)
mintFlow(to: user, amount: 1_000.0)
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user)
// Open position with pushToDrawDownSink = true (creates debt)
let openRes = _executeTransaction(
"../transactions/flow-alp/position/create_position.cdc",
[100.0, FLOW_VAULT_STORAGE_PATH, true],
user
)
Test.expect(openRes, Test.beSucceeded())
// Verify MOET was borrowed
let moetBalance = getBalance(address: user.address, vaultPublicPath: MOET.VaultPublicPath)!
log("Borrowed MOET: \(moetBalance)")
Test.assert(moetBalance > 0.0)
// Close position (ID 1 since test 1 created position 0)
let closeRes = _executeTransaction(
"../transactions/flow-alp/position/repay_and_close_position.cdc",
[UInt64(1)],
user
)
Test.expect(closeRes, Test.beSucceeded())
log("✅ Successfully closed position with debt: \(moetBalance) MOET")
}
// =============================================================================
// Test 3: Close with precision shortfall after multiple rebalances
// =============================================================================
access(all)
fun test_closePosition_precisionShortfall_multipleRebalances() {
log("\n=== Test: Close with Precision Shortfall (Multiple Rebalances) ===")
// Reset price to 1.0 for this test
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0)
// Reuse existing pool from previous test
let user = Test.createAccount()
setupMoetVault(user, beFailed: false)
mintFlow(to: user, amount: 1_000.0)
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user)
// Open position
let openRes = _executeTransaction(
"../transactions/flow-alp/position/create_position.cdc",
[100.0, FLOW_VAULT_STORAGE_PATH, true],
user
)
Test.expect(openRes, Test.beSucceeded())
// Perform rebalances with varying prices to accumulate rounding errors
log("\nRebalance 1: FLOW price = $1.2")
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.2)
let reb1 = _executeTransaction("../transactions/flow-alp/pool-management/rebalance_position.cdc", [UInt64(2), true], PROTOCOL_ACCOUNT)
Test.expect(reb1, Test.beSucceeded())
log("\nRebalance 2: FLOW price = $1.9")
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 0.9)
let reb2 = _executeTransaction("../transactions/flow-alp/pool-management/rebalance_position.cdc", [UInt64(2), true], PROTOCOL_ACCOUNT)
Test.expect(reb2, Test.beSucceeded())
log("\nRebalance 3: FLOW price = $1.5")
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.5)
let reb3 = _executeTransaction("../transactions/flow-alp/pool-management/rebalance_position.cdc", [UInt64(2), true], PROTOCOL_ACCOUNT)
Test.expect(reb3, Test.beSucceeded())
// Get final position state
let finalDetails = getPositionDetails(pid: 2, beFailed: false)
log("\n--- Final State ---")
log("Health: \(finalDetails.health)")
logBalances(finalDetails.balances)
// Close position - may have tiny shortfall due to accumulated rounding
let closeRes = _executeTransaction(
"../transactions/flow-alp/position/repay_and_close_position.cdc",
[UInt64(2)],
user
)
Test.expect(closeRes, Test.beSucceeded())
log("✅ Successfully closed after 3 rebalances (precision shortfall automatically handled)")
}
// =============================================================================
// Test 4: Demonstrate precision with extreme volatility
// =============================================================================
access(all)
fun test_closePosition_extremeVolatility() {
log("\n=== Test: Close After Extreme Price Volatility ===")
// Reset price to 1.0 for this test
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0)
// Reuse existing pool from previous test
let user = Test.createAccount()
setupMoetVault(user, beFailed: false)
mintFlow(to: user, amount: 1_000.0)
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user)
// Open position
let openRes = _executeTransaction(
"../transactions/flow-alp/position/create_position.cdc",
[100.0, FLOW_VAULT_STORAGE_PATH, true],
user
)
Test.expect(openRes, Test.beSucceeded())
// Simulate extreme volatility: 5x gains, 90% drops
let extremePrices: [UFix64] = [5.0, 0.5, 3.0, 0.2, 4.0, 0.1, 2.0]
var volCount = 1
for price in extremePrices {
log("\nExtreme volatility \(volCount): FLOW = $\(price)")
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: price)
let rebalanceRes = _executeTransaction(
"../transactions/flow-alp/pool-management/rebalance_position.cdc",
[UInt64(3), true],
PROTOCOL_ACCOUNT
)
Test.expect(rebalanceRes, Test.beSucceeded())
let details = getPositionDetails(pid: 3, beFailed: false)
log("Health: \(details.health)")
volCount = volCount + 1
}
log("\n--- Closing after extreme volatility ---")
// Close position
let closeRes = _executeTransaction(
"../transactions/flow-alp/position/repay_and_close_position.cdc",
[UInt64(3)],
user
)
Test.expect(closeRes, Test.beSucceeded())
log("✅ Successfully closed after extreme volatility (balance increased/fell dramatically)")
}
// =============================================================================
// Test 5: Close position with insufficient debt repayment
// =============================================================================
access(all)
fun test_closePosition_insufficientRepayment() {
log("\n=== Test: Close Position with Insufficient Debt Repayment ===")
setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0)
let user = Test.createAccount()
setupMoetVault(user, beFailed: false)
mintFlow(to: user, amount: 1_000.0)
grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user)
// Open position with debt — borrowed MOET is pushed to user's MOET vault (position 7)
let openRes = _executeTransaction(
"../transactions/flow-alp/position/create_position.cdc",
[100.0, FLOW_VAULT_STORAGE_PATH, true],
user
)
Test.expect(openRes, Test.beSucceeded())
let debt = getBalance(address: user.address, vaultPublicPath: MOET.VaultPublicPath)!
log("Borrowed MOET (= debt): \(debt)")
Test.assert(debt > 0.0)
let shortfall = 0.00000001
// Transfer a tiny amount away so user has (debt - 1 satoshi), one short of what's needed
let other = Test.createAccount()
setupMoetVault(other, beFailed: false)
let transferTx = Test.Transaction(
code: Test.readFile("../transactions/moet/transfer_moet.cdc"),
authorizers: [user.address],
signers: [user],
arguments: [other.address, shortfall]
)
let transferRes = Test.executeTransaction(transferTx)
Test.expect(transferRes, Test.beSucceeded())
let remainingMoet = getBalance(address: user.address, vaultPublicPath: MOET.VaultPublicPath)!
log("MOET remaining after transfer: \(remainingMoet)")
Test.assertEqual(debt - shortfall, remainingMoet)
// Attempt to close — source has 0 MOET but debt requires repayment
let closeRes = _executeTransaction(
"../transactions/flow-alp/position/repay_and_close_position.cdc",
[UInt64(4)],
user
)
Test.expect(closeRes, Test.beFailed())
Test.assertError(closeRes, errorMessage: "Insufficient funds from source")
log("✅ Close correctly failed with insufficient repayment")
}
// =============================================================================
// Helper Functions
// =============================================================================
access(all) fun logBalances(_ balances: [FlowALPv0.PositionBalance]) {
for balance in balances {
let direction = balance.direction == FlowALPv0.BalanceDirection.Credit ? "Credit" : "Debit"
log(" \(direction): \(balance.balance) of \(balance.vaultType.identifier)")
}
}