Skip to content
Open
Show file tree
Hide file tree
Changes from all 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