From 65bd30034259a1769bd5d270466d6fa00fbe3eb9 Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Tue, 24 Mar 2026 14:42:05 +0200 Subject: [PATCH 1/5] 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 31a11385..16a88c07 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -1882,6 +1882,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 details of a given position as a PositionDetails external struct access(all) fun getPositionDetails(pid: UInt64): PositionDetails { if self.debugLogging { 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 25b1ea33c522a78d9b760fbeac3ab5f49d26ee69 Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Wed, 25 Mar 2026 14:08:23 +0200 Subject: [PATCH 2/5] 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 c96ff8e9bc8304ac6f515d11469131955444a473 Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Wed, 25 Mar 2026 15:15:36 +0200 Subject: [PATCH 3/5] 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 3586ba50..1fe5083d 100644 --- a/cadence/scripts/flow-alp/get_position_by_id.cdc +++ b/cadence/scripts/flow-alp/get_position_by_id.cdc @@ -1,11 +1,11 @@ +// Returns the details of a specific position by its ID. import "FlowALPv0" -access(all) fun main(poolAddress: Address, positionID: UInt64): FlowALPv0.PositionDetails { - let account = getAccount(poolAddress) +access(all) fun main(positionID: 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 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 2b9dbdd1..06fbd02e 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -790,6 +790,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): Bool { From 97a876a8a495e32a6f18a2fba267a2bc87de497c 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/5] 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 16a88c07..c53b2c44 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -1883,7 +1883,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 2dd24d284ea5c30777e34129401b19191c8c9ff5 Mon Sep 17 00:00:00 2001 From: Illia Malachyn Date: Thu, 26 Mar 2026 12:14:02 +0200 Subject: [PATCH 5/5] 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 06fbd02e..6afe0cfa 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -800,6 +800,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(