-
Notifications
You must be signed in to change notification settings - Fork 2
Nialexsan/close position #188
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
fdc137d
20f9d81
0a464a4
ebe265a
8fd49d3
760b53d
1833914
8bcc4e2
015a9d8
972ac4c
d9970e3
3417621
f158d12
2a2e552
8e0f6f7
824c388
47a9e67
94df1fc
76d6b6c
94ae8ce
77ab388
ed16a72
ad92e44
ebf1c8c
253be6f
ca37d21
b8366f5
edf96dc
0b1831c
cd7c16c
f5d99da
cbd9f10
182a5ff
aed49a1
355e1c6
d9f0b6c
6c722af
1b42f8a
f2b7859
c008602
a11028e
c864297
0ed237b
252e658
7a769c8
0117b9c
5e19226
cb98c6b
ae8e3b9
530f342
901a226
f5de92d
114fa5c
1b3c6fa
f43e2e0
2531367
918c314
434e177
cb17a21
a1fbfb1
79bdb58
fa07368
4a93edd
047a6a7
6dafb4e
ce96de4
3f464d7
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
|
|
@@ -5,9 +5,11 @@ on: | |
| push: | ||
| branches: | ||
| - main | ||
| - v0 | ||
| pull_request: | ||
| branches: | ||
| - main | ||
| - v0 | ||
|
|
||
| jobs: | ||
| tests: | ||
|
|
||
Large diffs are not rendered by default.
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -0,0 +1,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) | ||
|
|
||
| // Verify FLOW collateral was deposited | ||
| let flowBalanceAfterDeposit = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)! | ||
| log("FLOW balance after deposit: \(flowBalanceAfterDeposit)") | ||
| Test.assert(flowBalanceAfterDeposit < 1_000.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()) | ||
|
|
||
| // Verify FLOW collateral was returned | ||
| let flowBalanceAfterPositionClosed = getBalance(address: user.address, vaultPublicPath: /public/flowTokenReceiver)! | ||
| log("FLOW balance after position closed: \(flowBalanceAfterPositionClosed)") | ||
| Test.assert(flowBalanceAfterPositionClosed == 1_000.0) | ||
|
|
||
| log("✅ Successfully closed position with debt: \(moetBalance) MOET") | ||
| } | ||
|
|
||
| // ============================================================================= | ||
|
Member
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. What about test cases with multiple debt/collateral balances?
Collaborator
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. multi collateral/debt test case will be invalidated by #184 |
||
| // 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") | ||
| } | ||
|
|
||
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we verify in the test that the debt was actually repaid and collateral returned to the user's account?