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
21 changes: 11 additions & 10 deletions cadence/contracts/FlowALPv0.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -176,16 +176,6 @@ access(all) contract FlowALPv0 {

/* --- CONSTRUCTS & INTERNAL METHODS ---- */

/// EPosition
///
/// Entitlement for managing positions within the pool.
/// This entitlement grants access to position-specific operations including deposits, withdrawals,
/// rebalancing, and health parameter management for any position in the pool.
///
/// Note that this entitlement provides access to all positions in the pool,
/// not just individual position owners' positions.
access(all) entitlement EPosition

/// ERebalance
///
/// Entitlement for rebalancing positions.
Expand All @@ -211,6 +201,17 @@ access(all) contract FlowALPv0 {
/// and process queued operations. It should not be granted to external users.
access(all) entitlement EImplementation

/// EPosition
///
/// Entitlement for managing positions within the pool.
/// This entitlement grants access to position-specific operations including deposits, withdrawals,
/// rebalancing, and health parameter management for any position in the pool.
///
/// IMPORTANT: this entitlement provides access to ALL positions in the pool and is for internal
/// implementation use only! Users access their individual positions via the Position resource.
access(all) entitlement EPosition


/// EParticipant
///
/// Entitlement for general participant operations that allow users to interact with the pool
Expand Down
42 changes: 17 additions & 25 deletions cadence/tests/contracts/AdversarialReentrancyConnectors.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -106,17 +106,18 @@ access(all) contract AdversarialReentrancyConnectors {
}

access(all) resource LiveData {
/// Optional: Pool capability for recursive withdrawAndPull call
access(all) var recursivePool: Capability<auth(FlowALPv0.EPosition) &FlowALPv0.Pool>?
/// Optional: Position ID for recursive withdrawAndPull call
/// Capability to the attacker's PositionManager for recursive withdrawal
access(all) var positionManagerCap: Capability<auth(FungibleToken.Withdraw, FlowALPv0.EPositionAdmin) &FlowALPv0.PositionManager>?
/// Position ID for recursive withdrawal
access(all) var recursivePositionID: UInt64?

init() { self.recursivePositionID = nil; self.recursivePool = nil }
access(all) fun setRecursivePool(_ pool: Capability<auth(FlowALPv0.EPosition) &FlowALPv0.Pool>) {
self.recursivePool = pool
}
access(all) fun setRecursivePositionID(_ positionID: UInt64) {
self.recursivePositionID = positionID
init() { self.recursivePositionID = nil; self.positionManagerCap = nil }
access(all) fun setRecursivePosition(
managerCap: Capability<auth(FungibleToken.Withdraw, FlowALPv0.EPositionAdmin) &FlowALPv0.PositionManager>,
pid: UInt64
) {
self.positionManagerCap = managerCap
self.recursivePositionID = pid
}
}
access(all) fun createLiveData(): @LiveData {
Expand Down Expand Up @@ -202,27 +203,18 @@ access(all) contract AdversarialReentrancyConnectors {
access(FungibleToken.Withdraw) fun withdrawAvailable(maxAmount: UFix64): @{FungibleToken.Vault} {
// If recursive withdrawAndPull is configured, call it first
log("VaultSource.withdrawAvailable called with maxAmount: \(maxAmount)")
log("=====Recursive pool: \(self.liveDataCap.check())")
log("=====Recursive position manager: \(self.liveDataCap.check())")
let liveData = self.liveDataCap.borrow() ?? panic("cant borrow LiveData")
let poolRef = liveData.recursivePool!.borrow() ?? panic("cant borrow Recursive pool is nil")
// Call withdrawAndPull on the position
let recursiveVault <- poolRef.withdrawAndPull(
pid: liveData.recursivePositionID!,
// type: Type<@MOET.Vault>(),
let manager = liveData.positionManagerCap!.borrow() ?? panic("cant borrow PositionManager")
let position = manager.borrowAuthorizedPosition(pid: liveData.recursivePositionID!)
// Attempt reentrant withdrawal via Position (should fail due to position lock)
let recursiveVault <- position.withdraw(
type: Type<@FlowToken.Vault>(),
// type: tokenType,
amount: 900.0,
pullFromTopUpSource: false
amount: 900.0
)
log("Recursive withdrawAndPull returned vault with balance: \(recursiveVault.balance)")
// If we got funds from the recursive call, return them
if recursiveVault.balance > 0.0 {
return <-recursiveVault
}
// Otherwise, destroy the empty vault and continue with normal withdrawal
log("Recursive withdraw succeeded with balance: \(recursiveVault.balance) (should not reach here)")
destroy recursiveVault


// Normal vault withdrawal
let available = self.minimumAvailable()
if !self.withdrawVault.check() || available == 0.0 || maxAmount == 0.0 {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,15 +3,15 @@ import "FlowALPv0"
transaction(adminAddr: Address) {

prepare(user: auth(SaveValue, LoadValue, ClaimInboxCapability) &Account) {
let claimed: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool> =
let claimed =
user.inbox.claim<
auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool
auth(FlowALPv0.EParticipant) &FlowALPv0.Pool
>("FlowALPv0BetaCap", provider: adminAddr)
?? panic("No beta capability found in inbox")

if user.storage.type(at: FlowALPv0.PoolCapStoragePath) != nil {
let _ = user.storage.load<
Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>
Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>
>(from: FlowALPv0.PoolCapStoragePath)
}
user.storage.save(claimed, to: FlowALPv0.PoolCapStoragePath)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import "FlowALPv0"
transaction(grantee: Address) {

prepare(admin: auth(IssueStorageCapabilityController, PublishInboxCapability) &Account) {
let poolCap: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool> =
let poolCap =
admin.capabilities.storage.issue<
auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool
auth(FlowALPv0.EParticipant) &FlowALPv0.Pool
>(FlowALPv0.PoolStoragePath)

assert(poolCap.check(), message: "Failed to issue beta capability")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -6,14 +6,14 @@ transaction() {
admin: auth(Capabilities, Storage) &Account,
tester: auth(Storage) &Account
) {
let poolCap: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool> =
let poolCap =
admin.capabilities.storage.issue<
auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool
auth(FlowALPv0.EParticipant) &FlowALPv0.Pool
>(FlowALPv0.PoolStoragePath)
// assert(poolCap.check(), message: "Failed to issue Pool capability")

if tester.storage.type(at: FlowALPv0.PoolCapStoragePath) != nil {
tester.storage.load<Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>>(
tester.storage.load<Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>>(
from: FlowALPv0.PoolCapStoragePath
)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ import "DummyConnectors"

transaction {
prepare(admin: auth(BorrowValue, Storage, Capabilities) &Account) {
let pool = admin.storage.borrow<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>(from: FlowALPv0.PoolStoragePath)
let pool = admin.storage.borrow<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>(from: FlowALPv0.PoolStoragePath)

// Ensure PositionManager exists
if admin.storage.borrow<&FlowALPv0.PositionManager>(from: FlowALPv0.PositionStoragePath) == nil {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -27,7 +27,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
// the position manager in the signer's account where we should store the new position
let positionManager: auth(FlowALPv0.EPositionAdmin) &FlowALPv0.PositionManager
// the authorized Pool capability
let poolCap: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>
let poolCap: Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>
// reference to signer's account for saving capability back
let signerAccount: auth(LoadValue, BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account

Expand Down Expand Up @@ -81,7 +81,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
?? panic("PositionManager not found")

// Load the authorized Pool capability from storage
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>>(
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>>(
from: FlowALPv0.PoolCapStoragePath
) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement")
}
Expand All @@ -102,10 +102,12 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B

self.positionManager.addPosition(position: <-position)
let sourceRef = self.source as! AdversarialReentrancyConnectors.VaultSourceHacked

let liveData = sourceRef.liveDataCap.borrow() ?? panic("cant borrow LiveData")
liveData.setRecursivePool(self.poolCap)
liveData.setRecursivePositionID(pid)
let managerCap = self.signerAccount.capabilities.storage.issue<
auth(FungibleToken.Withdraw, FlowALPv0.EPositionAdmin) &FlowALPv0.PositionManager
>(FlowALPv0.PositionStoragePath)
liveData.setRecursivePosition(managerCap: managerCap, pid: pid)

self.signerAccount.storage.save(self.poolCap, to: FlowALPv0.PoolCapStoragePath)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -28,7 +28,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
// the position manager in the signer's account where we should store the new position
let positionManager: auth(FlowALPv0.EPositionAdmin) &FlowALPv0.PositionManager
// the authorized Pool capability
let poolCap: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>
let poolCap: Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>
// reference to signer's account for saving capability back
let signerAccount: auth(LoadValue,BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account

Expand Down Expand Up @@ -81,7 +81,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
?? panic("PositionManager not found")

// Load the authorized Pool capability from storage
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>>(
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>>(
from: FlowALPv0.PoolCapStoragePath
) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ transaction(adminAddr: Address) {
prepare(user: auth(SaveValue, LoadValue, ClaimInboxCapability) &Account) {
// Save claimed cap at the protocol-defined storage path to satisfy consumers/tests expecting this path
let capPath = FlowALPv0.PoolCapStoragePath
let claimed: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool> =
let claimed =
user.inbox.claim<
auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool
auth(FlowALPv0.EParticipant) &FlowALPv0.Pool
>("FlowALPv0BetaCap", provider: adminAddr)
?? panic("No beta capability found in inbox")

if user.storage.type(at: capPath) != nil {
let _ = user.storage.load<Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>>(from: capPath)
let _ = user.storage.load<Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>>(from: capPath)
}
user.storage.save(claimed, to: capPath)
}
Expand Down
4 changes: 2 additions & 2 deletions cadence/transactions/flow-alp/beta/publish_beta_cap.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import "FlowALPv0"
transaction(grantee: Address) {

prepare(admin: auth(IssueStorageCapabilityController, PublishInboxCapability) &Account) {
let poolCap: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool> =
let poolCap =
admin.capabilities.storage.issue<
auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool
auth(FlowALPv0.EParticipant) &FlowALPv0.Pool
>(FlowALPv0.PoolStoragePath)

assert(poolCap.check(), message: "Failed to issue beta capability")
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,10 +7,10 @@ import "FlowALPv0"
/// the position is beyond its min/max health. If `true`, the rebalance executes regardless of its relative health.
///
transaction(pid: UInt64, force: Bool) {
let pool: auth(FlowALPv0.EPosition) &FlowALPv0.Pool
let pool: auth(FlowALPv0.ERebalance) &FlowALPv0.Pool

prepare(signer: auth(BorrowValue) &Account) {
self.pool = signer.storage.borrow<auth(FlowALPv0.EPosition) &FlowALPv0.Pool>(from: FlowALPv0.PoolStoragePath)
self.pool = signer.storage.borrow<auth(FlowALPv0.ERebalance) &FlowALPv0.Pool>(from: FlowALPv0.PoolStoragePath)
?? panic("Could not borrow reference to Pool from \(FlowALPv0.PoolStoragePath) - ensure a Pool has been configured")
}

Expand Down
4 changes: 2 additions & 2 deletions cadence/transactions/flow-alp/position/create_position.cdc
Original file line number Diff line number Diff line change
Expand Up @@ -20,7 +20,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
// the position manager in the signer's account where we should store the new position
let positionManager: auth(FlowALPv0.EPositionAdmin) &FlowALPv0.PositionManager
// the authorized Pool capability
let poolCap: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>
let poolCap: Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>
// reference to signer's account for saving capability back
let signerAccount: auth(Storage) &Account

Expand Down Expand Up @@ -76,7 +76,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
?? panic("PositionManager not found")

// Load the authorized Pool capability from storage
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>>(
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>>(
from: FlowALPv0.PoolCapStoragePath
) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement")
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -18,7 +18,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
// this DeFiActions Source that will allow for the repayment of a loan if the position becomes undercollateralized
let source: {DeFiActions.Source}
// the authorized Pool capability
let poolCap: Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>
let poolCap: Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>
// reference to signer's account for saving capability back
let signerAccount: auth(Storage) &Account

Expand Down Expand Up @@ -59,7 +59,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B
)

// Load the authorized Pool capability from storage
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant, FlowALPv0.EPosition) &FlowALPv0.Pool>>(
self.poolCap = signer.storage.load<Capability<auth(FlowALPv0.EParticipant) &FlowALPv0.Pool>>(
from: FlowALPv0.PoolCapStoragePath
) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement")
}
Expand Down
Loading