From b12dec3717e89c95d3465a136c702a3c6ca0d6ca Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Tue, 24 Mar 2026 14:42:05 +0200 Subject: [PATCH 1/6] Add function and script that return IDs of open positions --- cadence/contracts/FlowALPv0.cdc | 5 +++++ cadence/scripts/flow-alp/get_position_ids.cdc | 11 +++++++++++ 2 files changed, 16 insertions(+) create mode 100644 cadence/scripts/flow-alp/get_position_ids.cdc diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 224bf8e1..a62bc1fc 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -496,6 +496,11 @@ access(all) contract FlowALPv0 { ) } + /// Returns the IDs of all currently open positions in this pool + access(all) fun getPositionIDs(): [UInt64] { + return self.positions.keys + } + /// Returns the queued deposit balances for a given position. access(all) fun getQueuedDeposits(pid: UInt64): {Type: UFix64} { let position = self._borrowPosition(pid: pid) diff --git a/cadence/scripts/flow-alp/get_position_ids.cdc b/cadence/scripts/flow-alp/get_position_ids.cdc new file mode 100644 index 00000000..ddad867d --- /dev/null +++ b/cadence/scripts/flow-alp/get_position_ids.cdc @@ -0,0 +1,11 @@ +import "FlowALPv0" + +access(all) fun main(poolAddress: Address, poolUUID: UInt64): [UInt64] { + let account = getAccount(poolAddress) + + let poolRef = account.capabilities + .borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) + ?? panic("Could not borrow Pool reference from \(poolAddress)") + + return poolRef.getPositionIDs() +} From 476c8f720b126d3157f6eabab68db3d9eea6b6a2 Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Wed, 25 Mar 2026 14:08:23 +0200 Subject: [PATCH 2/6] add test for getPositionIDs function --- cadence/tests/get_position_ids_test.cdc | 145 ++++++++++++++++++++++++ 1 file changed, 145 insertions(+) create mode 100644 cadence/tests/get_position_ids_test.cdc diff --git a/cadence/tests/get_position_ids_test.cdc b/cadence/tests/get_position_ids_test.cdc new file mode 100644 index 00000000..d1198498 --- /dev/null +++ b/cadence/tests/get_position_ids_test.cdc @@ -0,0 +1,145 @@ +import Test +import BlockchainHelpers + +import "MOET" +import "FlowALPv0" +import "test_helpers.cdc" + +// ----------------------------------------------------------------------------- +// getPositionIDs Test +// +// Verifies that Pool.getPositionIDs() correctly reflects opened and closed +// positions via the get_position_ids.cdc script. +// ----------------------------------------------------------------------------- + +access(all) var snapshot: UInt64 = 0 + +access(all) +fun setup() { + deployContracts() + snapshot = getCurrentBlockHeight() +} + +// Helper: call the get_position_ids script and return the result array +access(all) +fun getPositionIDs(): [UInt64] { + let res = _executeScript( + "../scripts/flow-alp/get_position_ids.cdc", + [PROTOCOL_ACCOUNT.address, UInt64(0)] + ) + Test.expect(res, Test.beSucceeded()) + return res.returnValue as! [UInt64] +} + +// Helper: repay and close a position by ID +access(all) +fun closePosition(user: Test.TestAccount, positionID: UInt64) { + let res = _executeTransaction( + "../transactions/flow-alp/position/repay_and_close_position.cdc", + [positionID], + user + ) + Test.expect(res, Test.beSucceeded()) +} + +// ============================================================================= +// Test: getPositionIDs tracks opens and closes correctly +// ============================================================================= +access(all) +fun test_getPositionIDs_lifecycle() { + // --- Setup --- + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0) + + 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: 10_000.0) + grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user) + + // --- No positions yet --- + var ids = getPositionIDs() + Test.assertEqual(0, ids.length) + + // --- Open position 0 (with borrow) --- + let open0 = _executeTransaction( + "../transactions/flow-alp/position/create_position.cdc", + [100.0, FLOW_VAULT_STORAGE_PATH, true], + user + ) + Test.expect(open0, Test.beSucceeded()) + + ids = getPositionIDs() + Test.assertEqual(1, ids.length) + Test.assert(ids.contains(UInt64(0)), message: "Expected position 0 in IDs") + + // --- Open position 1 (with borrow) --- + let open1 = _executeTransaction( + "../transactions/flow-alp/position/create_position.cdc", + [100.0, FLOW_VAULT_STORAGE_PATH, true], + user + ) + Test.expect(open1, Test.beSucceeded()) + + ids = getPositionIDs() + Test.assertEqual(2, ids.length) + Test.assert(ids.contains(UInt64(0)), message: "Expected position 0 in IDs") + Test.assert(ids.contains(UInt64(1)), message: "Expected position 1 in IDs") + + // --- Open position 2 (no borrow, so closing won't need MOET repay) --- + let open2 = _executeTransaction( + "../transactions/flow-alp/position/create_position.cdc", + [100.0, FLOW_VAULT_STORAGE_PATH, false], + user + ) + Test.expect(open2, Test.beSucceeded()) + + ids = getPositionIDs() + Test.assertEqual(3, ids.length) + Test.assert(ids.contains(UInt64(2)), message: "Expected position 2 in IDs") + + // --- Close position 2 (no debt, straightforward) --- + closePosition(user: user, positionID: 2) + + ids = getPositionIDs() + Test.assertEqual(2, ids.length) + Test.assert(!ids.contains(UInt64(2)), message: "Position 2 should be removed after close") + Test.assert(ids.contains(UInt64(0)), message: "Position 0 should still exist") + Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist") + + // --- Close position 0 (has debt, repay needed) --- + closePosition(user: user, positionID: 0) + + ids = getPositionIDs() + Test.assertEqual(1, ids.length) + Test.assert(!ids.contains(UInt64(0)), message: "Position 0 should be removed after close") + Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist") + + // --- Open position 3 (new position after some closures) --- + let open3 = _executeTransaction( + "../transactions/flow-alp/position/create_position.cdc", + [100.0, FLOW_VAULT_STORAGE_PATH, true], + user + ) + Test.expect(open3, Test.beSucceeded()) + + ids = getPositionIDs() + Test.assertEqual(2, ids.length) + Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist") + Test.assert(ids.contains(UInt64(3)), message: "Expected position 3 in IDs") + + // --- Close remaining positions --- + closePosition(user: user, positionID: 1) + closePosition(user: user, positionID: 3) + + ids = getPositionIDs() + Test.assertEqual(0, ids.length) +} From 9616b7b0da2f93f5882a7c7c9b9d39282bb4214f Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Wed, 25 Mar 2026 15:15:36 +0200 Subject: [PATCH 3/6] remove unused arguments --- .../scripts/flow-alp/get_position_by_id.cdc | 16 +++--- cadence/scripts/flow-alp/get_position_ids.cdc | 14 ++--- cadence/tests/get_position_ids_test.cdc | 51 ++----------------- cadence/tests/test_helpers.cdc | 20 ++++++++ 4 files changed, 39 insertions(+), 62 deletions(-) diff --git a/cadence/scripts/flow-alp/get_position_by_id.cdc b/cadence/scripts/flow-alp/get_position_by_id.cdc index 785a89ca..f019f73d 100644 --- a/cadence/scripts/flow-alp/get_position_by_id.cdc +++ b/cadence/scripts/flow-alp/get_position_by_id.cdc @@ -1,12 +1,12 @@ +// Returns the details of a specific position by its ID. import "FlowALPv0" import "FlowALPModels" -access(all) fun main(poolAddress: Address, positionID: UInt64): FlowALPModels.PositionDetails { - let account = getAccount(poolAddress) +access(all) fun main(positionID: UInt64): FlowALPModels.PositionDetails { + let protocolAddress = Type<@FlowALPv0.Pool>().address! + let account = getAccount(protocolAddress) + let pool = account.capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) + ?? panic("Could not find Pool at path \(FlowALPv0.PoolPublicPath)") - let poolRef = account.capabilities - .borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) - ?? panic("Could not borrow Pool reference from \(poolAddress)") - - return poolRef.getPositionDetails(pid: positionID) -} \ No newline at end of file + return pool.getPositionDetails(pid: positionID) +} diff --git a/cadence/scripts/flow-alp/get_position_ids.cdc b/cadence/scripts/flow-alp/get_position_ids.cdc index ddad867d..016dacec 100644 --- a/cadence/scripts/flow-alp/get_position_ids.cdc +++ b/cadence/scripts/flow-alp/get_position_ids.cdc @@ -1,11 +1,11 @@ +// Returns the IDs of all currently open positions in the pool. import "FlowALPv0" -access(all) fun main(poolAddress: Address, poolUUID: UInt64): [UInt64] { - let account = getAccount(poolAddress) +access(all) fun main(): [UInt64] { + let protocolAddress = Type<@FlowALPv0.Pool>().address! + let account = getAccount(protocolAddress) + let pool = account.capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) + ?? panic("Could not find Pool at path \(FlowALPv0.PoolPublicPath)") - let poolRef = account.capabilities - .borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) - ?? panic("Could not borrow Pool reference from \(poolAddress)") - - return poolRef.getPositionIDs() + return pool.getPositionIDs() } diff --git a/cadence/tests/get_position_ids_test.cdc b/cadence/tests/get_position_ids_test.cdc index d1198498..dd8343f4 100644 --- a/cadence/tests/get_position_ids_test.cdc +++ b/cadence/tests/get_position_ids_test.cdc @@ -20,28 +20,6 @@ fun setup() { snapshot = getCurrentBlockHeight() } -// Helper: call the get_position_ids script and return the result array -access(all) -fun getPositionIDs(): [UInt64] { - let res = _executeScript( - "../scripts/flow-alp/get_position_ids.cdc", - [PROTOCOL_ACCOUNT.address, UInt64(0)] - ) - Test.expect(res, Test.beSucceeded()) - return res.returnValue as! [UInt64] -} - -// Helper: repay and close a position by ID -access(all) -fun closePosition(user: Test.TestAccount, positionID: UInt64) { - let res = _executeTransaction( - "../transactions/flow-alp/position/repay_and_close_position.cdc", - [positionID], - user - ) - Test.expect(res, Test.beSucceeded()) -} - // ============================================================================= // Test: getPositionIDs tracks opens and closes correctly // ============================================================================= @@ -63,31 +41,20 @@ fun test_getPositionIDs_lifecycle() { let user = Test.createAccount() setupMoetVault(user, beFailed: false) mintFlow(to: user, amount: 10_000.0) - grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user) // --- No positions yet --- var ids = getPositionIDs() Test.assertEqual(0, ids.length) // --- Open position 0 (with borrow) --- - let open0 = _executeTransaction( - "../transactions/flow-alp/position/create_position.cdc", - [100.0, FLOW_VAULT_STORAGE_PATH, true], - user - ) - Test.expect(open0, Test.beSucceeded()) + createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) ids = getPositionIDs() Test.assertEqual(1, ids.length) Test.assert(ids.contains(UInt64(0)), message: "Expected position 0 in IDs") // --- Open position 1 (with borrow) --- - let open1 = _executeTransaction( - "../transactions/flow-alp/position/create_position.cdc", - [100.0, FLOW_VAULT_STORAGE_PATH, true], - user - ) - Test.expect(open1, Test.beSucceeded()) + createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) ids = getPositionIDs() Test.assertEqual(2, ids.length) @@ -95,12 +62,7 @@ fun test_getPositionIDs_lifecycle() { Test.assert(ids.contains(UInt64(1)), message: "Expected position 1 in IDs") // --- Open position 2 (no borrow, so closing won't need MOET repay) --- - let open2 = _executeTransaction( - "../transactions/flow-alp/position/create_position.cdc", - [100.0, FLOW_VAULT_STORAGE_PATH, false], - user - ) - Test.expect(open2, Test.beSucceeded()) + createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) ids = getPositionIDs() Test.assertEqual(3, ids.length) @@ -124,12 +86,7 @@ fun test_getPositionIDs_lifecycle() { Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist") // --- Open position 3 (new position after some closures) --- - let open3 = _executeTransaction( - "../transactions/flow-alp/position/create_position.cdc", - [100.0, FLOW_VAULT_STORAGE_PATH, true], - user - ) - Test.expect(open3, Test.beSucceeded()) + createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) ids = getPositionIDs() Test.assertEqual(2, ids.length) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 1d422c6c..64eb434d 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -1062,6 +1062,26 @@ fun withdrawReserve( Test.expect(txRes, beFailed ? Test.beFailed() : Test.beSucceeded()) } +access(all) +fun getPositionIDs(): [UInt64] { + let res = _executeScript( + "../scripts/flow-alp/get_position_ids.cdc", + [] + ) + Test.expect(res, Test.beSucceeded()) + return res.returnValue as! [UInt64] +} + +access(all) +fun closePosition(user: Test.TestAccount, positionID: UInt64) { + let res = _executeTransaction( + "../transactions/flow-alp/position/repay_and_close_position.cdc", + [positionID], + user + ) + Test.expect(res, Test.beSucceeded()) +} + /* --- Assertion Helpers --- */ access(all) fun equalWithinVariance(_ expected: AnyStruct, _ actual: AnyStruct, _ variance: AnyStruct): Bool { From d563e91ad54fafb5a45b31af6ab5b6298be0d60a Mon Sep 17 00:00:00 2001 From: Alex <12097569+nialexsan@users.noreply.github.com> Date: Wed, 25 Mar 2026 11:13:40 -0400 Subject: [PATCH 4/6] Apply suggestion from @nialexsan --- cadence/contracts/FlowALPv0.cdc | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index a62bc1fc..a9057f50 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -497,7 +497,7 @@ access(all) contract FlowALPv0 { } /// Returns the IDs of all currently open positions in this pool - access(all) fun getPositionIDs(): [UInt64] { + access(all) view fun getPositionIDs(): [UInt64] { return self.positions.keys } From 2e87f9b068246eca52d0574b57a46033c5e67a90 Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Thu, 26 Mar 2026 12:14:02 +0200 Subject: [PATCH 5/6] add get_positions_by_ids script --- .../scripts/flow-alp/get_positions_by_ids.cdc | 15 ++++ cadence/tests/get_position_ids_test.cdc | 3 - cadence/tests/get_positions_by_ids_test.cdc | 68 +++++++++++++++++++ cadence/tests/test_helpers.cdc | 10 +++ 4 files changed, 93 insertions(+), 3 deletions(-) create mode 100644 cadence/scripts/flow-alp/get_positions_by_ids.cdc create mode 100644 cadence/tests/get_positions_by_ids_test.cdc diff --git a/cadence/scripts/flow-alp/get_positions_by_ids.cdc b/cadence/scripts/flow-alp/get_positions_by_ids.cdc new file mode 100644 index 00000000..ce4ca48f --- /dev/null +++ b/cadence/scripts/flow-alp/get_positions_by_ids.cdc @@ -0,0 +1,15 @@ +// Returns the details of multiple positions by their IDs. +import "FlowALPv0" + +access(all) fun main(positionIDs: [UInt64]): [FlowALPv0.PositionDetails] { + let protocolAddress = Type<@FlowALPv0.Pool>().address! + let account = getAccount(protocolAddress) + let pool = account.capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) + ?? panic("Could not find Pool at path \(FlowALPv0.PoolPublicPath)") + + let details: [FlowALPv0.PositionDetails] = [] + for id in positionIDs { + details.append(pool.getPositionDetails(pid: id)) + } + return details +} diff --git a/cadence/tests/get_position_ids_test.cdc b/cadence/tests/get_position_ids_test.cdc index dd8343f4..0873a7ee 100644 --- a/cadence/tests/get_position_ids_test.cdc +++ b/cadence/tests/get_position_ids_test.cdc @@ -12,12 +12,9 @@ import "test_helpers.cdc" // positions via the get_position_ids.cdc script. // ----------------------------------------------------------------------------- -access(all) var snapshot: UInt64 = 0 - access(all) fun setup() { deployContracts() - snapshot = getCurrentBlockHeight() } // ============================================================================= diff --git a/cadence/tests/get_positions_by_ids_test.cdc b/cadence/tests/get_positions_by_ids_test.cdc new file mode 100644 index 00000000..4648f3bc --- /dev/null +++ b/cadence/tests/get_positions_by_ids_test.cdc @@ -0,0 +1,68 @@ +import Test +import BlockchainHelpers + +import "MOET" +import "FlowALPv0" +import "test_helpers.cdc" + +// ----------------------------------------------------------------------------- +// getPositionsByIDs Test +// +// Verifies that the get_positions_by_ids.cdc script correctly returns position +// details for the requested IDs. +// ----------------------------------------------------------------------------- + +access(all) +fun setup() { + deployContracts() +} + +// ============================================================================= +// Test: getPositionsByIDs returns correct details for multiple positions +// ============================================================================= +access(all) +fun test_getPositionsByIDs() { + // --- Setup --- + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0) + + 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: 10_000.0) + + // --- Open two positions --- + createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) + createPosition(signer: user, amount: 200.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) + + // --- Fetch both positions by IDs --- + let details = getPositionsByIDs(positionIDs: [UInt64(0), UInt64(1)]) + Test.assertEqual(2, details.length) + + // Verify each result matches the single-position helper + let details0 = getPositionDetails(pid: 0, beFailed: false) + let details1 = getPositionDetails(pid: 1, beFailed: false) + + Test.assertEqual(details0.health, details[0].health) + Test.assertEqual(details0.balances.length, details[0].balances.length) + + Test.assertEqual(details1.health, details[1].health) + Test.assertEqual(details1.balances.length, details[1].balances.length) + + // --- Empty input returns empty array --- + let emptyDetails = getPositionsByIDs(positionIDs: []) + Test.assertEqual(0, emptyDetails.length) + + // --- Single ID works --- + let singleDetails = getPositionsByIDs(positionIDs: [UInt64(0)]) + Test.assertEqual(1, singleDetails.length) + Test.assertEqual(details0.health, singleDetails[0].health) +} diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 64eb434d..782bb3ac 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -1072,6 +1072,16 @@ fun getPositionIDs(): [UInt64] { return res.returnValue as! [UInt64] } +access(all) +fun getPositionsByIDs(positionIDs: [UInt64]): [FlowALPv0.PositionDetails] { + let res = _executeScript( + "../scripts/flow-alp/get_positions_by_ids.cdc", + [positionIDs] + ) + Test.expect(res, Test.beSucceeded()) + return res.returnValue as! [FlowALPv0.PositionDetails] +} + access(all) fun closePosition(user: Test.TestAccount, positionID: UInt64) { let res = _executeTransaction( From 1d1214c0e3b06b7907e80d83450dff4b445b0a92 Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Thu, 26 Mar 2026 13:39:56 +0200 Subject: [PATCH 6/6] Rename position scripts to filter only open positions Since the pool contract does not remove positions from its dictionary on close (balance keys persist with zero values), the previous scripts returned stale/closed position IDs. Rename to get_open_position_ids and get_open_positions_by_ids, and filter by checking for non-zero balances so only truly open positions are returned. --- .../flow-alp/get_open_position_ids.cdc | 29 ++++++++++ .../flow-alp/get_open_positions_by_ids.cdc | 29 ++++++++++ cadence/scripts/flow-alp/get_position_ids.cdc | 11 ---- .../scripts/flow-alp/get_positions_by_ids.cdc | 15 ----- ...est.cdc => get_open_position_ids_test.cdc} | 56 +++++++------------ ...cdc => get_open_positions_by_ids_test.cdc} | 29 ++++++---- cadence/tests/test_helpers.cdc | 10 ++-- 7 files changed, 101 insertions(+), 78 deletions(-) create mode 100644 cadence/scripts/flow-alp/get_open_position_ids.cdc create mode 100644 cadence/scripts/flow-alp/get_open_positions_by_ids.cdc delete mode 100644 cadence/scripts/flow-alp/get_position_ids.cdc delete mode 100644 cadence/scripts/flow-alp/get_positions_by_ids.cdc rename cadence/tests/{get_position_ids_test.cdc => get_open_position_ids_test.cdc} (54%) rename cadence/tests/{get_positions_by_ids_test.cdc => get_open_positions_by_ids_test.cdc} (61%) diff --git a/cadence/scripts/flow-alp/get_open_position_ids.cdc b/cadence/scripts/flow-alp/get_open_position_ids.cdc new file mode 100644 index 00000000..0a26c52a --- /dev/null +++ b/cadence/scripts/flow-alp/get_open_position_ids.cdc @@ -0,0 +1,29 @@ +// Returns the IDs of all currently open positions in the pool. +// A position is considered open if it has at least one non-zero balance. +import "FlowALPv0" + +access(all) fun main(): [UInt64] { + let protocolAddress = Type<@FlowALPv0.Pool>().address! + let account = getAccount(protocolAddress) + let pool = account.capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) + ?? panic("Could not find Pool at path \(FlowALPv0.PoolPublicPath)") + + let allIDs = pool.getPositionIDs() + let openIDs: [UInt64] = [] + for id in allIDs { + let details = pool.getPositionDetails(pid: id) + var hasBalance = false + for balance in details.balances { + if balance.balance > 0.0 { + hasBalance = true + break + } + } + + if hasBalance { + openIDs.append(id) + } + } + + return openIDs +} diff --git a/cadence/scripts/flow-alp/get_open_positions_by_ids.cdc b/cadence/scripts/flow-alp/get_open_positions_by_ids.cdc new file mode 100644 index 00000000..d579b98f --- /dev/null +++ b/cadence/scripts/flow-alp/get_open_positions_by_ids.cdc @@ -0,0 +1,29 @@ +// Returns the details of open positions for the given IDs. +// Positions with no non-zero balances (closed) are excluded from the result. +import "FlowALPv0" +import "FlowALPModels" + +access(all) fun main(positionIDs: [UInt64]): [FlowALPModels.PositionDetails] { + let protocolAddress = Type<@FlowALPv0.Pool>().address! + let account = getAccount(protocolAddress) + let pool = account.capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) + ?? panic("Could not find Pool at path \(FlowALPv0.PoolPublicPath)") + + let details: [FlowALPModels.PositionDetails] = [] + for id in positionIDs { + let d = pool.getPositionDetails(pid: id) + var hasBalance = false + for balance in d.balances { + if balance.balance > 0.0 { + hasBalance = true + break + } + } + + if hasBalance { + details.append(d) + } + } + + return details +} diff --git a/cadence/scripts/flow-alp/get_position_ids.cdc b/cadence/scripts/flow-alp/get_position_ids.cdc deleted file mode 100644 index 016dacec..00000000 --- a/cadence/scripts/flow-alp/get_position_ids.cdc +++ /dev/null @@ -1,11 +0,0 @@ -// Returns the IDs of all currently open positions in the pool. -import "FlowALPv0" - -access(all) fun main(): [UInt64] { - let protocolAddress = Type<@FlowALPv0.Pool>().address! - let account = getAccount(protocolAddress) - let pool = account.capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) - ?? panic("Could not find Pool at path \(FlowALPv0.PoolPublicPath)") - - return pool.getPositionIDs() -} diff --git a/cadence/scripts/flow-alp/get_positions_by_ids.cdc b/cadence/scripts/flow-alp/get_positions_by_ids.cdc deleted file mode 100644 index ce4ca48f..00000000 --- a/cadence/scripts/flow-alp/get_positions_by_ids.cdc +++ /dev/null @@ -1,15 +0,0 @@ -// Returns the details of multiple positions by their IDs. -import "FlowALPv0" - -access(all) fun main(positionIDs: [UInt64]): [FlowALPv0.PositionDetails] { - let protocolAddress = Type<@FlowALPv0.Pool>().address! - let account = getAccount(protocolAddress) - let pool = account.capabilities.borrow<&FlowALPv0.Pool>(FlowALPv0.PoolPublicPath) - ?? panic("Could not find Pool at path \(FlowALPv0.PoolPublicPath)") - - let details: [FlowALPv0.PositionDetails] = [] - for id in positionIDs { - details.append(pool.getPositionDetails(pid: id)) - } - return details -} diff --git a/cadence/tests/get_position_ids_test.cdc b/cadence/tests/get_open_position_ids_test.cdc similarity index 54% rename from cadence/tests/get_position_ids_test.cdc rename to cadence/tests/get_open_position_ids_test.cdc index 0873a7ee..e15e7f6c 100644 --- a/cadence/tests/get_position_ids_test.cdc +++ b/cadence/tests/get_open_position_ids_test.cdc @@ -6,10 +6,10 @@ import "FlowALPv0" import "test_helpers.cdc" // ----------------------------------------------------------------------------- -// getPositionIDs Test +// getOpenPositionIDs Test // -// Verifies that Pool.getPositionIDs() correctly reflects opened and closed -// positions via the get_position_ids.cdc script. +// Verifies that get_open_position_ids.cdc correctly returns only IDs of +// positions that have at least one non-zero balance. // ----------------------------------------------------------------------------- access(all) @@ -18,10 +18,10 @@ fun setup() { } // ============================================================================= -// Test: getPositionIDs tracks opens and closes correctly +// Test: getOpenPositionIDs tracks opens and closes correctly // ============================================================================= access(all) -fun test_getPositionIDs_lifecycle() { +fun test_getOpenPositionIDs_lifecycle() { // --- Setup --- setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0) @@ -40,60 +40,44 @@ fun test_getPositionIDs_lifecycle() { mintFlow(to: user, amount: 10_000.0) // --- No positions yet --- - var ids = getPositionIDs() + var ids = getOpenPositionIDs() Test.assertEqual(0, ids.length) - // --- Open position 0 (with borrow) --- - createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) + // --- Open position 0 (no borrow) --- + createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) - ids = getPositionIDs() + ids = getOpenPositionIDs() Test.assertEqual(1, ids.length) Test.assert(ids.contains(UInt64(0)), message: "Expected position 0 in IDs") - // --- Open position 1 (with borrow) --- - createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) + // --- Open position 1 (no borrow) --- + createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 200.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) - ids = getPositionIDs() + ids = getOpenPositionIDs() Test.assertEqual(2, ids.length) Test.assert(ids.contains(UInt64(0)), message: "Expected position 0 in IDs") Test.assert(ids.contains(UInt64(1)), message: "Expected position 1 in IDs") - // --- Open position 2 (no borrow, so closing won't need MOET repay) --- - createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) - - ids = getPositionIDs() - Test.assertEqual(3, ids.length) - Test.assert(ids.contains(UInt64(2)), message: "Expected position 2 in IDs") - - // --- Close position 2 (no debt, straightforward) --- - closePosition(user: user, positionID: 2) - - ids = getPositionIDs() - Test.assertEqual(2, ids.length) - Test.assert(!ids.contains(UInt64(2)), message: "Position 2 should be removed after close") - Test.assert(ids.contains(UInt64(0)), message: "Position 0 should still exist") - Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist") - - // --- Close position 0 (has debt, repay needed) --- + // --- Close position 0 --- closePosition(user: user, positionID: 0) - ids = getPositionIDs() + ids = getOpenPositionIDs() Test.assertEqual(1, ids.length) Test.assert(!ids.contains(UInt64(0)), message: "Position 0 should be removed after close") Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist") - // --- Open position 3 (new position after some closures) --- - createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) + // --- Open position 2 --- + createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) - ids = getPositionIDs() + ids = getOpenPositionIDs() Test.assertEqual(2, ids.length) Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist") - Test.assert(ids.contains(UInt64(3)), message: "Expected position 3 in IDs") + Test.assert(ids.contains(UInt64(2)), message: "Expected position 2 in IDs") // --- Close remaining positions --- closePosition(user: user, positionID: 1) - closePosition(user: user, positionID: 3) + closePosition(user: user, positionID: 2) - ids = getPositionIDs() + ids = getOpenPositionIDs() Test.assertEqual(0, ids.length) } diff --git a/cadence/tests/get_positions_by_ids_test.cdc b/cadence/tests/get_open_positions_by_ids_test.cdc similarity index 61% rename from cadence/tests/get_positions_by_ids_test.cdc rename to cadence/tests/get_open_positions_by_ids_test.cdc index 4648f3bc..06399912 100644 --- a/cadence/tests/get_positions_by_ids_test.cdc +++ b/cadence/tests/get_open_positions_by_ids_test.cdc @@ -6,10 +6,10 @@ import "FlowALPv0" import "test_helpers.cdc" // ----------------------------------------------------------------------------- -// getPositionsByIDs Test +// getOpenPositionsByIDs Test // -// Verifies that the get_positions_by_ids.cdc script correctly returns position -// details for the requested IDs. +// Verifies that the get_open_positions_by_ids.cdc script correctly returns +// position details only for open positions (those with non-zero balances). // ----------------------------------------------------------------------------- access(all) @@ -18,10 +18,10 @@ fun setup() { } // ============================================================================= -// Test: getPositionsByIDs returns correct details for multiple positions +// Test: getOpenPositionsByIDs returns correct details and filters closed // ============================================================================= access(all) -fun test_getPositionsByIDs() { +fun test_getOpenPositionsByIDs() { // --- Setup --- setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: FLOW_TOKEN_IDENTIFIER, price: 1.0) @@ -39,12 +39,12 @@ fun test_getPositionsByIDs() { setupMoetVault(user, beFailed: false) mintFlow(to: user, amount: 10_000.0) - // --- Open two positions --- - createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: true) - createPosition(signer: user, amount: 200.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) + // --- Open two positions (no borrow to avoid MOET cross-contamination) --- + createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) + createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 200.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) // --- Fetch both positions by IDs --- - let details = getPositionsByIDs(positionIDs: [UInt64(0), UInt64(1)]) + let details = getOpenPositionsByIDs(positionIDs: [UInt64(0), UInt64(1)]) Test.assertEqual(2, details.length) // Verify each result matches the single-position helper @@ -58,11 +58,18 @@ fun test_getPositionsByIDs() { Test.assertEqual(details1.balances.length, details[1].balances.length) // --- Empty input returns empty array --- - let emptyDetails = getPositionsByIDs(positionIDs: []) + let emptyDetails = getOpenPositionsByIDs(positionIDs: []) Test.assertEqual(0, emptyDetails.length) // --- Single ID works --- - let singleDetails = getPositionsByIDs(positionIDs: [UInt64(0)]) + let singleDetails = getOpenPositionsByIDs(positionIDs: [UInt64(0)]) Test.assertEqual(1, singleDetails.length) Test.assertEqual(details0.health, singleDetails[0].health) + + // --- Close position 1 and verify it's filtered out --- + closePosition(user: user, positionID: 1) + + let afterClose = getOpenPositionsByIDs(positionIDs: [UInt64(0), UInt64(1)]) + Test.assertEqual(1, afterClose.length) + Test.assertEqual(details0.health, afterClose[0].health) } diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 782bb3ac..7f1d7dc1 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -1063,9 +1063,9 @@ fun withdrawReserve( } access(all) -fun getPositionIDs(): [UInt64] { +fun getOpenPositionIDs(): [UInt64] { let res = _executeScript( - "../scripts/flow-alp/get_position_ids.cdc", + "../scripts/flow-alp/get_open_position_ids.cdc", [] ) Test.expect(res, Test.beSucceeded()) @@ -1073,13 +1073,13 @@ fun getPositionIDs(): [UInt64] { } access(all) -fun getPositionsByIDs(positionIDs: [UInt64]): [FlowALPv0.PositionDetails] { +fun getOpenPositionsByIDs(positionIDs: [UInt64]): [FlowALPModels.PositionDetails] { let res = _executeScript( - "../scripts/flow-alp/get_positions_by_ids.cdc", + "../scripts/flow-alp/get_open_positions_by_ids.cdc", [positionIDs] ) Test.expect(res, Test.beSucceeded()) - return res.returnValue as! [FlowALPv0.PositionDetails] + return res.returnValue as! [FlowALPModels.PositionDetails] } access(all)