diff --git a/cadence/contracts/FlowALPv0.cdc b/cadence/contracts/FlowALPv0.cdc index 52c40a90..1ec06fcc 100644 --- a/cadence/contracts/FlowALPv0.cdc +++ b/cadence/contracts/FlowALPv0.cdc @@ -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. @@ -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 diff --git a/cadence/tests/contracts/AdversarialReentrancyConnectors.cdc b/cadence/tests/contracts/AdversarialReentrancyConnectors.cdc index 5d69260d..b85dc891 100644 --- a/cadence/tests/contracts/AdversarialReentrancyConnectors.cdc +++ b/cadence/tests/contracts/AdversarialReentrancyConnectors.cdc @@ -106,17 +106,18 @@ access(all) contract AdversarialReentrancyConnectors { } access(all) resource LiveData { - /// Optional: Pool capability for recursive withdrawAndPull call - access(all) var recursivePool: Capability? - /// Optional: Position ID for recursive withdrawAndPull call + /// Capability to the attacker's PositionManager for recursive withdrawal + access(all) var positionManagerCap: Capability? + /// Position ID for recursive withdrawal access(all) var recursivePositionID: UInt64? - init() { self.recursivePositionID = nil; self.recursivePool = nil } - access(all) fun setRecursivePool(_ pool: Capability) { - 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, + pid: UInt64 + ) { + self.positionManagerCap = managerCap + self.recursivePositionID = pid } } access(all) fun createLiveData(): @LiveData { @@ -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 { diff --git a/cadence/tests/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc b/cadence/tests/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc index f5695230..a6df9b9e 100644 --- a/cadence/tests/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc +++ b/cadence/tests/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc @@ -3,15 +3,15 @@ import "FlowALPv0" transaction(adminAddr: Address) { prepare(user: auth(SaveValue, LoadValue, ClaimInboxCapability) &Account) { - let claimed: Capability = + 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 + Capability >(from: FlowALPv0.PoolCapStoragePath) } user.storage.save(claimed, to: FlowALPv0.PoolCapStoragePath) diff --git a/cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc b/cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc index d6776e47..5700212e 100644 --- a/cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc +++ b/cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc @@ -3,9 +3,9 @@ import "FlowALPv0" transaction(grantee: Address) { prepare(admin: auth(IssueStorageCapabilityController, PublishInboxCapability) &Account) { - let poolCap: Capability = + 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") diff --git a/cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc b/cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc index 2d6d6ad0..68c8d8ac 100644 --- a/cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc +++ b/cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc @@ -6,14 +6,14 @@ transaction() { admin: auth(Capabilities, Storage) &Account, tester: auth(Storage) &Account ) { - let poolCap: Capability = + 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>( + tester.storage.load>( from: FlowALPv0.PoolCapStoragePath ) } diff --git a/cadence/tests/transactions/flow-alp/pool-management/04_create_position.cdc b/cadence/tests/transactions/flow-alp/pool-management/04_create_position.cdc index d42aa857..5659f3a6 100644 --- a/cadence/tests/transactions/flow-alp/pool-management/04_create_position.cdc +++ b/cadence/tests/transactions/flow-alp/pool-management/04_create_position.cdc @@ -7,7 +7,7 @@ import "DummyConnectors" transaction { prepare(admin: auth(BorrowValue, Storage, Capabilities) &Account) { - let pool = admin.storage.borrow(from: FlowALPv0.PoolStoragePath) + let pool = admin.storage.borrow(from: FlowALPv0.PoolStoragePath) // Ensure PositionManager exists if admin.storage.borrow<&FlowALPv0.PositionManager>(from: FlowALPv0.PositionStoragePath) == nil { diff --git a/cadence/tests/transactions/position-manager/create_position_reentrancy.cdc b/cadence/tests/transactions/position-manager/create_position_reentrancy.cdc index 4500178c..c7cb9ceb 100644 --- a/cadence/tests/transactions/position-manager/create_position_reentrancy.cdc +++ b/cadence/tests/transactions/position-manager/create_position_reentrancy.cdc @@ -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 + let poolCap: Capability // reference to signer's account for saving capability back let signerAccount: auth(LoadValue, BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account @@ -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>( + self.poolCap = signer.storage.load>( from: FlowALPv0.PoolCapStoragePath ) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement") } @@ -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) } diff --git a/cadence/tests/transactions/position-manager/create_position_spoofing_source.cdc b/cadence/tests/transactions/position-manager/create_position_spoofing_source.cdc index 6c0e8bae..57551b92 100644 --- a/cadence/tests/transactions/position-manager/create_position_spoofing_source.cdc +++ b/cadence/tests/transactions/position-manager/create_position_spoofing_source.cdc @@ -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 + let poolCap: Capability // reference to signer's account for saving capability back let signerAccount: auth(LoadValue,BorrowValue, SaveValue, IssueStorageCapabilityController, PublishCapability, UnpublishCapability) &Account @@ -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>( + self.poolCap = signer.storage.load>( from: FlowALPv0.PoolCapStoragePath ) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement") } diff --git a/cadence/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc b/cadence/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc index 020738c4..e9c5c0e2 100644 --- a/cadence/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc +++ b/cadence/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc @@ -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 = + 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>(from: capPath) + let _ = user.storage.load>(from: capPath) } user.storage.save(claimed, to: capPath) } diff --git a/cadence/transactions/flow-alp/beta/publish_beta_cap.cdc b/cadence/transactions/flow-alp/beta/publish_beta_cap.cdc index c07e0151..18f60fbe 100644 --- a/cadence/transactions/flow-alp/beta/publish_beta_cap.cdc +++ b/cadence/transactions/flow-alp/beta/publish_beta_cap.cdc @@ -3,9 +3,9 @@ import "FlowALPv0" transaction(grantee: Address) { prepare(admin: auth(IssueStorageCapabilityController, PublishInboxCapability) &Account) { - let poolCap: Capability = + 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") diff --git a/cadence/transactions/flow-alp/pool-management/rebalance_position.cdc b/cadence/transactions/flow-alp/pool-management/rebalance_position.cdc index 0c3fa3d9..ee1df5ab 100644 --- a/cadence/transactions/flow-alp/pool-management/rebalance_position.cdc +++ b/cadence/transactions/flow-alp/pool-management/rebalance_position.cdc @@ -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(from: FlowALPv0.PoolStoragePath) + self.pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) ?? panic("Could not borrow reference to Pool from \(FlowALPv0.PoolStoragePath) - ensure a Pool has been configured") } diff --git a/cadence/transactions/flow-alp/position/create_position.cdc b/cadence/transactions/flow-alp/position/create_position.cdc index 97b91675..0ee259da 100644 --- a/cadence/transactions/flow-alp/position/create_position.cdc +++ b/cadence/transactions/flow-alp/position/create_position.cdc @@ -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 + let poolCap: Capability // reference to signer's account for saving capability back let signerAccount: auth(Storage) &Account @@ -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>( + self.poolCap = signer.storage.load>( from: FlowALPv0.PoolCapStoragePath ) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement") } diff --git a/cadence/transactions/flow-alp/position/create_position_not_managed.cdc b/cadence/transactions/flow-alp/position/create_position_not_managed.cdc index cce01a99..27f70ee8 100644 --- a/cadence/transactions/flow-alp/position/create_position_not_managed.cdc +++ b/cadence/transactions/flow-alp/position/create_position_not_managed.cdc @@ -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 + let poolCap: Capability // reference to signer's account for saving capability back let signerAccount: auth(Storage) &Account @@ -59,7 +59,7 @@ transaction(amount: UFix64, vaultStoragePath: StoragePath, pushToDrawDownSink: B ) // Load the authorized Pool capability from storage - self.poolCap = signer.storage.load>( + self.poolCap = signer.storage.load>( from: FlowALPv0.PoolCapStoragePath ) ?? panic("Could not load Pool capability from storage - ensure the signer has been granted Pool access with EParticipant entitlement") }