diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 31a11385..8fd878e5 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -1882,6 +1882,18 @@ access(all) contract FlowALPv0 { ) } + /// Returns the number of currently open positions in this pool + access(all) fun getPositionCount(): UInt64 { + return UInt64(self.positions.keys.length) + } + + /// Returns the details of the position at the given index into the active key list. + access(all) fun getPositionDetailsAtIndex(index: UInt64): PositionDetails { + assert(index < UInt64(self.positions.keys.length), message: "Index out of bounds") + let pid = self.positions.keys[Int(index)] + return self.getPositionDetails(pid: pid) + } + /// 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_positions.cdc b/cadence/scripts/flow-alp/get_positions.cdc new file mode 100644 index 00000000..448aea9a --- /dev/null +++ b/cadence/scripts/flow-alp/get_positions.cdc @@ -0,0 +1,33 @@ +// Returns up to `count` position details starting from index `startIndex` +// in the pool's position IDs array. +import "FlowALPv0" + +access(all) fun main(startIndex: UInt64, count: 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 total = pool.getPositionCount() + + if startIndex >= total { + return [] + } + + var endIndex: UInt64 = 0 + if startIndex + count > total { + endIndex = total + } else { + endIndex = startIndex + count + } + + var positions: [FlowALPv0.PositionDetails] = [] + var i = startIndex + + while i < endIndex { + positions.append(pool.getPositionDetailsAtIndex(index: i)) + i = i + 1 + } + + return positions +} diff --git a/cadence/tests/get_positions_test.cdc b/cadence/tests/get_positions_test.cdc new file mode 100644 index 00000000..9ac305fd --- /dev/null +++ b/cadence/tests/get_positions_test.cdc @@ -0,0 +1,87 @@ +import Test +import BlockchainHelpers + +import "MOET" +import "FlowALPv0" +import "test_helpers.cdc" + +// ----------------------------------------------------------------------------- +// get_positions.cdc Test +// +// Verifies paginated retrieval of position details via startIndex and count. +// ----------------------------------------------------------------------------- + +access(all) var snapshot: UInt64 = 0 + +access(all) +fun setup() { + deployContracts() + snapshot = getCurrentBlockHeight() +} + +// ============================================================================= +// Test: pagination returns correct slices of positions +// ============================================================================= +access(all) +fun test_getPositions_pagination() { + // --- 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) + + // --- Empty pool --- + var positions = getPositions(startIndex: 0, count: 10) + Test.assertEqual(0, positions.length) + + // --- Open 4 positions (no borrow for easy cleanup) --- + createPosition(signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) + createPosition(signer: user, amount: 200.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) + createPosition(signer: user, amount: 300.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) + createPosition(signer: user, amount: 400.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) + + // --- Fetch all at once --- + positions = getPositions(startIndex: 0, count: 10) + Test.assertEqual(4, positions.length) + + // --- Fetch first 2 --- + positions = getPositions(startIndex: 0, count: 2) + Test.assertEqual(2, positions.length) + + // --- Fetch next 2 --- + positions = getPositions(startIndex: 2, count: 2) + Test.assertEqual(2, positions.length) + + // --- Fetch with startIndex beyond length --- + positions = getPositions(startIndex: 10, count: 5) + Test.assertEqual(0, positions.length) + + // --- Fetch with count exceeding remaining --- + positions = getPositions(startIndex: 3, count: 100) + Test.assertEqual(1, positions.length) + + // --- Close position 1, verify pagination reflects removal --- + closePosition(user: user, positionID: 1) + + positions = getPositions(startIndex: 0, count: 10) + Test.assertEqual(3, positions.length) + + // First page of 2 + positions = getPositions(startIndex: 0, count: 2) + Test.assertEqual(2, positions.length) + + // Second page + positions = getPositions(startIndex: 2, count: 2) + Test.assertEqual(1, positions.length) +} diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 2b9dbdd1..d1ba516d 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 getPositions(startIndex: UInt64, count: UInt64): [FlowALPv0.PositionDetails] { + let res = _executeScript( + "../scripts/flow-alp/get_positions.cdc", + [startIndex, count] + ) + Test.expect(res, Test.beSucceeded()) + return res.returnValue as! [FlowALPv0.PositionDetails] +} + +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 {