Skip to content
Draft
Show file tree
Hide file tree
Changes from 6 commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions cadence/contracts/FlowALPv0.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -496,6 +496,11 @@ access(all) contract FlowALPv0 {
)
}

/// Returns the IDs of all currently open positions in this pool
access(all) view 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)
Expand Down
29 changes: 29 additions & 0 deletions cadence/scripts/flow-alp/get_open_position_ids.cdc
Original file line number Diff line number Diff line change
@@ -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
}
29 changes: 29 additions & 0 deletions cadence/scripts/flow-alp/get_open_positions_by_ids.cdc
Original file line number Diff line number Diff line change
@@ -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
}
16 changes: 8 additions & 8 deletions cadence/scripts/flow-alp/get_position_by_id.cdc
Original file line number Diff line number Diff line change
@@ -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)
}
return pool.getPositionDetails(pid: positionID)
}
83 changes: 83 additions & 0 deletions cadence/tests/get_open_position_ids_test.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,83 @@
import Test
import BlockchainHelpers

import "MOET"
import "FlowALPv0"
import "test_helpers.cdc"

// -----------------------------------------------------------------------------
// getOpenPositionIDs Test
//
// Verifies that get_open_position_ids.cdc correctly returns only IDs of
// positions that have at least one non-zero balance.
// -----------------------------------------------------------------------------

access(all)
fun setup() {
deployContracts()
}

// =============================================================================
// Test: getOpenPositionIDs tracks opens and closes correctly
// =============================================================================
access(all)
fun test_getOpenPositionIDs_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)

// --- No positions yet ---
var ids = getOpenPositionIDs()
Test.assertEqual(0, ids.length)

// --- Open position 0 (no borrow) ---
createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false)

ids = getOpenPositionIDs()
Test.assertEqual(1, ids.length)
Test.assert(ids.contains(UInt64(0)), message: "Expected position 0 in IDs")

// --- Open position 1 (no borrow) ---
createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 200.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false)

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")

// --- Close position 0 ---
closePosition(user: user, positionID: 0)

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 2 ---
createPosition(admin: PROTOCOL_ACCOUNT, signer: user, amount: 100.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false)

ids = getOpenPositionIDs()
Test.assertEqual(2, ids.length)
Test.assert(ids.contains(UInt64(1)), message: "Position 1 should still exist")
Test.assert(ids.contains(UInt64(2)), message: "Expected position 2 in IDs")

// --- Close remaining positions ---
closePosition(user: user, positionID: 1)
closePosition(user: user, positionID: 2)

ids = getOpenPositionIDs()
Test.assertEqual(0, ids.length)
}
75 changes: 75 additions & 0 deletions cadence/tests/get_open_positions_by_ids_test.cdc
Original file line number Diff line number Diff line change
@@ -0,0 +1,75 @@
import Test
import BlockchainHelpers

import "MOET"
import "FlowALPv0"
import "test_helpers.cdc"

// -----------------------------------------------------------------------------
// getOpenPositionsByIDs Test
//
// 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)
fun setup() {
deployContracts()
}

// =============================================================================
// Test: getOpenPositionsByIDs returns correct details and filters closed
// =============================================================================
access(all)
fun test_getOpenPositionsByIDs() {
// --- 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 (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 = getOpenPositionsByIDs(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 = getOpenPositionsByIDs(positionIDs: [])
Test.assertEqual(0, emptyDetails.length)

// --- Single ID works ---
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)
}
30 changes: 30 additions & 0 deletions cadence/tests/test_helpers.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -1062,6 +1062,36 @@ fun withdrawReserve(
Test.expect(txRes, beFailed ? Test.beFailed() : Test.beSucceeded())
}

access(all)
fun getOpenPositionIDs(): [UInt64] {
let res = _executeScript(
"../scripts/flow-alp/get_open_position_ids.cdc",
[]
)
Test.expect(res, Test.beSucceeded())
return res.returnValue as! [UInt64]
}

access(all)
fun getOpenPositionsByIDs(positionIDs: [UInt64]): [FlowALPModels.PositionDetails] {
let res = _executeScript(
"../scripts/flow-alp/get_open_positions_by_ids.cdc",
[positionIDs]
)
Test.expect(res, Test.beSucceeded())
return res.returnValue as! [FlowALPModels.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, _ variance: AnyStruct): Bool {
Expand Down
Loading