From c38635eebe901e7d9c6b0e3a848d7ebc657cfbff Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Thu, 19 Feb 2026 11:56:12 -0500 Subject: [PATCH 01/10] add security permission matrix doc Maps all FlowALPv0 entitlements to operations by resource, with plain-language descriptions. Intended for audit/security review. Co-Authored-By: Claude Sonnet 4.6 --- docs/security-permission-matrix.md | 60 ++++++++++++++++++++++++++++++ 1 file changed, 60 insertions(+) create mode 100644 docs/security-permission-matrix.md diff --git a/docs/security-permission-matrix.md b/docs/security-permission-matrix.md new file mode 100644 index 00000000..7c7b2390 --- /dev/null +++ b/docs/security-permission-matrix.md @@ -0,0 +1,60 @@ +# Security Permission Matrix + +Maps each entitlement to the operations it permits. For audit/security review. + +## Entitlements (FlowALPv0) + +| Entitlement | Who holds it | +|---|---| +| `EParticipant` | Any external user | +| `EPosition` | Position operators | +| `ERebalance` | Rebalancer contracts | +| `EPositionAdmin` | Position owner | +| `EGovernance` | Protocol admins only | +| `EImplementation` | Internal protocol use only | + +## Permission Matrix + +| Resource | Operation | Description | EParticipant | EPosition | ERebalance | EPositionAdmin | EGovernance | EImplementation | +|---|---|---|---|---|---|---|---|---| +| **Pool** | `createPosition` | A user can open a new position | ✅ | | | | | | +| **Pool** | `depositToPosition` | A user can deposit collateral | ✅ | | | | | | +| **Pool** | `withdraw` | An operator can withdraw from a position | | ✅ | | | | | +| **Pool** | `depositAndPush` | Push excess funds to drawdown sink | | ✅ | | | | | +| **Pool** | `withdrawAndPull` | Pull funds from top-up source on withdraw | | ✅ | | | | | +| **Pool** | `lockPosition` | Prevent updates to a position | | ✅ | | | | | +| **Pool** | `unlockPosition` | Re-enable updates to a position | | ✅ | | | | | +| **Pool** | `rebalancePosition` | Rebalance a position's health | | ✅ | ✅ | | | | +| **Pool** | `pausePool` / `unpausePool` | Halt or resume all pool operations | | | | | ✅ | | +| **Pool** | `addSupportedToken` | Add a new collateral/borrow token | | | | | ✅ | | +| **Pool** | `setInterestCurve` | Configure interest rate model | | | | | ✅ | | +| **Pool** | `setInsuranceRate` | Set the insurance fee rate | | | | | ✅ | | +| **Pool** | `setStabilityFeeRate` | Set the stability fee rate | | | | | ✅ | | +| **Pool** | `setLiquidationParams` | Configure liquidation thresholds | | | | | ✅ | | +| **Pool** | `setPauseParams` | Configure pause conditions | | | | | ✅ | | +| **Pool** | `setDepositLimitFraction` | Cap deposits as fraction of pool | | | | | ✅ | | +| **Pool** | `collectInsurance` | Sweep insurance fees to treasury | | | | | ✅ | | +| **Pool** | `withdrawStabilityFund` | Withdraw from stability reserve | | | | | ✅ | | +| **Pool** | `setDEX` / `setPriceOracle` | Set liquidation DEX or price feed | | | | | ✅ | | +| **Pool** | `asyncUpdate` | Process queued state updates (internal) | | | | | | ✅ | +| **Pool** | `regenerateAllDepositCapacities` | Recalculate deposit caps (internal) | | | | | | ✅ | +| **Position** | `rebalance` | Rebalance this position's health | | ✅ | ✅ | | | | +| **Position** | `setTargetHealth` | Set the health ratio the rebalancer aims for | | | | ✅ | | | +| **Position** | `setMinHealth` | Set the minimum health before auto-borrow | | | | ✅ | | | +| **Position** | `setMaxHealth` | Set the maximum health before auto-repay | | | | ✅ | | | +| **Position** | `provideSink` | Configure where excess funds are sent | | | | ✅ | | | +| **Position** | `provideSource` | Configure where top-up funds come from | | | | ✅ | | | +| **Position** | `asyncUpdatePosition` | Process queued update for this position (internal) | | | | | | ✅ | +| **PositionManager** | `addPosition` | Add a position to the manager | | | | ✅ | | | +| **PositionManager** | `removePosition` | Remove a position from the manager | | | | ✅ | | | +| **PositionManager** | `borrowAuthorizedPosition` | Borrow a position with withdrawal rights | | | | ✅ | | | +| **Rebalancer** | `setRecurringConfig` | Change the rebalance schedule/config | `Configure`¹ | | | | | | +| **RebalancerPaid** | `delete` | Stop and remove a paid rebalancer | `Delete`¹ | | | | | | + +¹ Contract-local entitlements in `FlowALPRebalancerv1` / `FlowALPRebalancerPaidv1`, not part of the FlowALPv0 hierarchy. + +## Audit Notes + +- `rebalancePosition` / `rebalance` use `EPosition | ERebalance` — **either** entitlement is sufficient (union, not conjunction) +- `borrowAuthorizedPosition` requires `FungibleToken.Withdraw + EPositionAdmin` — **both** required (conjunction) +- `EImplementation` maps to `Mutate + FungibleToken.Withdraw` via the `ImplementationUpdates` entitlement mapping — never issued to external accounts From 25fd067368aea53d93c540cda87cc89734760ef0 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:03:44 -0500 Subject: [PATCH 02/10] docs: highlight EPosition over-grant risk in permission matrix - Mark EPosition as protocol-internal only, not for end users - Add ownership-check warnings on all pool-level EPosition operations - Document the beta capability over-grant issue (EPosition -> EParticipant fix needed) Co-Authored-By: Claude Sonnet 4.6 --- docs/security-permission-matrix.md | 24 ++++++++++++++++-------- 1 file changed, 16 insertions(+), 8 deletions(-) diff --git a/docs/security-permission-matrix.md b/docs/security-permission-matrix.md index 7c7b2390..b66a2118 100644 --- a/docs/security-permission-matrix.md +++ b/docs/security-permission-matrix.md @@ -6,10 +6,10 @@ Maps each entitlement to the operations it permits. For audit/security review. | Entitlement | Who holds it | |---|---| -| `EParticipant` | Any external user | -| `EPosition` | Position operators | +| `EParticipant` | Any external user (beta users, public) | +| `EPosition` | ⚠️ Protocol-internal operators only — NOT end users. Grants pool-wide access to operate on **any** position by ID, with no ownership check. | | `ERebalance` | Rebalancer contracts | -| `EPositionAdmin` | Position owner | +| `EPositionAdmin` | Position owner only | | `EGovernance` | Protocol admins only | | `EImplementation` | Internal protocol use only | @@ -19,11 +19,11 @@ Maps each entitlement to the operations it permits. For audit/security review. |---|---|---|---|---|---|---|---|---| | **Pool** | `createPosition` | A user can open a new position | ✅ | | | | | | | **Pool** | `depositToPosition` | A user can deposit collateral | ✅ | | | | | | -| **Pool** | `withdraw` | An operator can withdraw from a position | | ✅ | | | | | -| **Pool** | `depositAndPush` | Push excess funds to drawdown sink | | ✅ | | | | | -| **Pool** | `withdrawAndPull` | Pull funds from top-up source on withdraw | | ✅ | | | | | -| **Pool** | `lockPosition` | Prevent updates to a position | | ✅ | | | | | -| **Pool** | `unlockPosition` | Re-enable updates to a position | | ✅ | | | | | +| **Pool** | `withdraw` | ⚠️ Withdraw from **any** position by ID — no ownership check | | ✅ | | | | | +| **Pool** | `depositAndPush` | ⚠️ Push funds from **any** position to its drawdown sink | | ✅ | | | | | +| **Pool** | `withdrawAndPull` | ⚠️ Withdraw from **any** position, pulling from its top-up source | | ✅ | | | | | +| **Pool** | `lockPosition` | ⚠️ Freeze **any** position from updates | | ✅ | | | | | +| **Pool** | `unlockPosition` | ⚠️ Unfreeze **any** position | | ✅ | | | | | | **Pool** | `rebalancePosition` | Rebalance a position's health | | ✅ | ✅ | | | | | **Pool** | `pausePool` / `unpausePool` | Halt or resume all pool operations | | | | | ✅ | | | **Pool** | `addSupportedToken` | Add a new collateral/borrow token | | | | | ✅ | | @@ -58,3 +58,11 @@ Maps each entitlement to the operations it permits. For audit/security review. - `rebalancePosition` / `rebalance` use `EPosition | ERebalance` — **either** entitlement is sufficient (union, not conjunction) - `borrowAuthorizedPosition` requires `FungibleToken.Withdraw + EPositionAdmin` — **both** required (conjunction) - `EImplementation` maps to `Mutate + FungibleToken.Withdraw` via the `ImplementationUpdates` entitlement mapping — never issued to external accounts + +## ⚠️ Known Issue: Beta Capability Over-Grant + +`publish_beta_cap.cdc` and `claim_and_save_beta_cap.cdc` currently grant `EParticipant + EPosition` to beta users. + +`EPosition` is **not needed** for normal user actions (create/deposit). Because `EPosition` gates pool-level `withdraw(pid:)` with no ownership check, any beta user can withdraw funds from **any other user's position**. + +**Fix:** Remove `EPosition` from the beta capability — grant `EParticipant` only. From 7a1ea97c9a7ff3a74394ca958ebad7269b3a91c0 Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:07:45 -0500 Subject: [PATCH 03/10] docs: restructure matrix by actor to expose beta over-grant clearly Replace resource-grouped columns with actor columns (User, User w/ EPosition, Rebalancer, Position Owner, Governance, Protocol Internal). The beta over-grant is now directly visible as a dedicated column showing what current beta users can do vs. what they should be able to do. Co-Authored-By: Claude Sonnet 4.6 --- docs/security-permission-matrix.md | 108 ++++++++++++++--------------- 1 file changed, 54 insertions(+), 54 deletions(-) diff --git a/docs/security-permission-matrix.md b/docs/security-permission-matrix.md index b66a2118..38eb8e21 100644 --- a/docs/security-permission-matrix.md +++ b/docs/security-permission-matrix.md @@ -2,67 +2,67 @@ Maps each entitlement to the operations it permits. For audit/security review. -## Entitlements (FlowALPv0) +## Entitlements → Actors -| Entitlement | Who holds it | -|---|---| -| `EParticipant` | Any external user (beta users, public) | -| `EPosition` | ⚠️ Protocol-internal operators only — NOT end users. Grants pool-wide access to operate on **any** position by ID, with no ownership check. | -| `ERebalance` | Rebalancer contracts | -| `EPositionAdmin` | Position owner only | -| `EGovernance` | Protocol admins only | -| `EImplementation` | Internal protocol use only | +| Entitlement | Actor | Entitlement granted by | +|---|---|---| +| `EParticipant` | User (beta / public) | `publish_beta_cap.cdc` | +| `EPosition` | ⚠️ Protocol operator (NOT end users) | `publish_beta_cap.cdc` ← **over-grant** | +| `ERebalance` | Rebalancer contract | Rebalancer setup | +| `EPositionAdmin` | Position owner | Position resource ownership | +| `EGovernance` | Protocol admin | Admin account | +| `EImplementation` | Protocol internals | Never issued externally | -## Permission Matrix +## Actor Capability Matrix -| Resource | Operation | Description | EParticipant | EPosition | ERebalance | EPositionAdmin | EGovernance | EImplementation | -|---|---|---|---|---|---|---|---|---| -| **Pool** | `createPosition` | A user can open a new position | ✅ | | | | | | -| **Pool** | `depositToPosition` | A user can deposit collateral | ✅ | | | | | | -| **Pool** | `withdraw` | ⚠️ Withdraw from **any** position by ID — no ownership check | | ✅ | | | | | -| **Pool** | `depositAndPush` | ⚠️ Push funds from **any** position to its drawdown sink | | ✅ | | | | | -| **Pool** | `withdrawAndPull` | ⚠️ Withdraw from **any** position, pulling from its top-up source | | ✅ | | | | | -| **Pool** | `lockPosition` | ⚠️ Freeze **any** position from updates | | ✅ | | | | | -| **Pool** | `unlockPosition` | ⚠️ Unfreeze **any** position | | ✅ | | | | | -| **Pool** | `rebalancePosition` | Rebalance a position's health | | ✅ | ✅ | | | | -| **Pool** | `pausePool` / `unpausePool` | Halt or resume all pool operations | | | | | ✅ | | -| **Pool** | `addSupportedToken` | Add a new collateral/borrow token | | | | | ✅ | | -| **Pool** | `setInterestCurve` | Configure interest rate model | | | | | ✅ | | -| **Pool** | `setInsuranceRate` | Set the insurance fee rate | | | | | ✅ | | -| **Pool** | `setStabilityFeeRate` | Set the stability fee rate | | | | | ✅ | | -| **Pool** | `setLiquidationParams` | Configure liquidation thresholds | | | | | ✅ | | -| **Pool** | `setPauseParams` | Configure pause conditions | | | | | ✅ | | -| **Pool** | `setDepositLimitFraction` | Cap deposits as fraction of pool | | | | | ✅ | | -| **Pool** | `collectInsurance` | Sweep insurance fees to treasury | | | | | ✅ | | -| **Pool** | `withdrawStabilityFund` | Withdraw from stability reserve | | | | | ✅ | | -| **Pool** | `setDEX` / `setPriceOracle` | Set liquidation DEX or price feed | | | | | ✅ | | -| **Pool** | `asyncUpdate` | Process queued state updates (internal) | | | | | | ✅ | -| **Pool** | `regenerateAllDepositCapacities` | Recalculate deposit caps (internal) | | | | | | ✅ | -| **Position** | `rebalance` | Rebalance this position's health | | ✅ | ✅ | | | | -| **Position** | `setTargetHealth` | Set the health ratio the rebalancer aims for | | | | ✅ | | | -| **Position** | `setMinHealth` | Set the minimum health before auto-borrow | | | | ✅ | | | -| **Position** | `setMaxHealth` | Set the maximum health before auto-repay | | | | ✅ | | | -| **Position** | `provideSink` | Configure where excess funds are sent | | | | ✅ | | | -| **Position** | `provideSource` | Configure where top-up funds come from | | | | ✅ | | | -| **Position** | `asyncUpdatePosition` | Process queued update for this position (internal) | | | | | | ✅ | -| **PositionManager** | `addPosition` | Add a position to the manager | | | | ✅ | | | -| **PositionManager** | `removePosition` | Remove a position from the manager | | | | ✅ | | | -| **PositionManager** | `borrowAuthorizedPosition` | Borrow a position with withdrawal rights | | | | ✅ | | | -| **Rebalancer** | `setRecurringConfig` | Change the rebalance schedule/config | `Configure`¹ | | | | | | -| **RebalancerPaid** | `delete` | Stop and remove a paid rebalancer | `Delete`¹ | | | | | | +| Operation | Description | User (EParticipant) | ⚠️ User w/ EPosition (current beta) | Rebalancer (ERebalance) | Position Owner (EPositionAdmin) | Governance (EGovernance) | Protocol Internal (EImplementation) | +|---|---|---|---|---|---|---|---| +| `createPosition` | Open a new position | ✅ | ✅ | | | | | +| `depositToPosition` | Deposit collateral | ✅ | ✅ | | | | | +| `withdraw` | ⚠️ Withdraw from **any** position by ID | | ✅ | | | | | +| `withdrawAndPull` | ⚠️ Withdraw from **any** position + pull top-up | | ✅ | | | | | +| `depositAndPush` | ⚠️ Push funds from **any** position | | ✅ | | | | | +| `lockPosition` | ⚠️ Freeze **any** position | | ✅ | | | | | +| `unlockPosition` | ⚠️ Unfreeze **any** position | | ✅ | | | | | +| `rebalancePosition` | Rebalance a position's health | | ✅ | ✅ | | | | +| `rebalance` (Position) | Rebalance this position | | ✅ | ✅ | | | | +| `setTargetHealth` | Set target health ratio | | | | ✅ | | | +| `setMinHealth` | Set min health before auto-borrow | | | | ✅ | | | +| `setMaxHealth` | Set max health before auto-repay | | | | ✅ | | | +| `provideSink` | Configure drawdown sink | | | | ✅ | | | +| `provideSource` | Configure top-up source | | | | ✅ | | | +| `addPosition` (Manager) | Add position to manager | | | | ✅ | | | +| `removePosition` (Manager) | Remove position from manager | | | | ✅ | | | +| `borrowAuthorizedPosition` | Borrow position with withdrawal rights¹ | | | | ✅ | | | +| `pausePool` / `unpausePool` | Halt or resume all pool operations | | | | | ✅ | | +| `addSupportedToken` | Add a new collateral/borrow token | | | | | ✅ | | +| `setInterestCurve` | Configure interest rate model | | | | | ✅ | | +| `setInsuranceRate` | Set the insurance fee rate | | | | | ✅ | | +| `setStabilityFeeRate` | Set the stability fee rate | | | | | ✅ | | +| `setLiquidationParams` | Configure liquidation thresholds | | | | | ✅ | | +| `setPauseParams` | Configure pause conditions | | | | | ✅ | | +| `setDepositLimitFraction` | Cap deposits as fraction of pool | | | | | ✅ | | +| `collectInsurance` | Sweep insurance fees to treasury | | | | | ✅ | | +| `withdrawStabilityFund` | Withdraw from stability reserve | | | | | ✅ | | +| `setDEX` / `setPriceOracle` | Set liquidation DEX or price feed | | | | | ✅ | | +| `asyncUpdate` | Process queued state updates | | | | | | ✅ | +| `asyncUpdatePosition` | Process queued update for one position | | | | | | ✅ | +| `regenerateAllDepositCapacities` | Recalculate all deposit caps | | | | | | ✅ | +| `setRecurringConfig` (Rebalancer) | Change rebalance schedule | `Configure`² | | | | | | +| `delete` (RebalancerPaid) | Stop and remove paid rebalancer | `Delete`² | | | | | | -¹ Contract-local entitlements in `FlowALPRebalancerv1` / `FlowALPRebalancerPaidv1`, not part of the FlowALPv0 hierarchy. - -## Audit Notes - -- `rebalancePosition` / `rebalance` use `EPosition | ERebalance` — **either** entitlement is sufficient (union, not conjunction) -- `borrowAuthorizedPosition` requires `FungibleToken.Withdraw + EPositionAdmin` — **both** required (conjunction) -- `EImplementation` maps to `Mutate + FungibleToken.Withdraw` via the `ImplementationUpdates` entitlement mapping — never issued to external accounts +¹ `borrowAuthorizedPosition` requires `FungibleToken.Withdraw + EPositionAdmin` — both required (conjunction). +² Contract-local entitlements in `FlowALPRebalancerv1` / `FlowALPRebalancerPaidv1`, not part of the FlowALPv0 hierarchy. ## ⚠️ Known Issue: Beta Capability Over-Grant -`publish_beta_cap.cdc` and `claim_and_save_beta_cap.cdc` currently grant `EParticipant + EPosition` to beta users. +`publish_beta_cap.cdc` grants `EParticipant + EPosition` to beta users (the "⚠️ User w/ EPosition" column above). -`EPosition` is **not needed** for normal user actions (create/deposit). Because `EPosition` gates pool-level `withdraw(pid:)` with no ownership check, any beta user can withdraw funds from **any other user's position**. +`EPosition` is **not needed** for normal user actions (create/deposit). The ⚠️ rows above are all unlocked for beta users, meaning any beta user can withdraw funds from or freeze **any other user's position**. **Fix:** Remove `EPosition` from the beta capability — grant `EParticipant` only. + +## Audit Notes + +- `rebalancePosition` / `rebalance` use `EPosition | ERebalance` — **either** entitlement is sufficient (union, not conjunction) +- `EImplementation` maps to `Mutate + FungibleToken.Withdraw` via the `ImplementationUpdates` entitlement mapping — never issued externally From 2780290b22d205076bd67c1e8c00a55266bb683f Mon Sep 17 00:00:00 2001 From: vishal <1117327+vishalchangrani@users.noreply.github.com> Date: Thu, 19 Feb 2026 12:13:58 -0500 Subject: [PATCH 04/10] docs: clarify EPositionAdmin is user acting on own positions via storage Co-Authored-By: Claude Sonnet 4.6 --- docs/security-permission-matrix.md | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/docs/security-permission-matrix.md b/docs/security-permission-matrix.md index 38eb8e21..379025cf 100644 --- a/docs/security-permission-matrix.md +++ b/docs/security-permission-matrix.md @@ -4,18 +4,18 @@ Maps each entitlement to the operations it permits. For audit/security review. ## Entitlements → Actors -| Entitlement | Actor | Entitlement granted by | +| Entitlement | Actor | How granted | |---|---|---| -| `EParticipant` | User (beta / public) | `publish_beta_cap.cdc` | -| `EPosition` | ⚠️ Protocol operator (NOT end users) | `publish_beta_cap.cdc` ← **over-grant** | +| `EParticipant` | User | Published capability (`publish_beta_cap.cdc`) | +| `EPosition` | ⚠️ Protocol operator (NOT end users) | Published capability (`publish_beta_cap.cdc`) ← **over-grant** | | `ERebalance` | Rebalancer contract | Rebalancer setup | -| `EPositionAdmin` | Position owner | Position resource ownership | +| `EPositionAdmin` | User (own positions only) | Storage ownership of `PositionManager` — cannot be delegated | | `EGovernance` | Protocol admin | Admin account | | `EImplementation` | Protocol internals | Never issued externally | ## Actor Capability Matrix -| Operation | Description | User (EParticipant) | ⚠️ User w/ EPosition (current beta) | Rebalancer (ERebalance) | Position Owner (EPositionAdmin) | Governance (EGovernance) | Protocol Internal (EImplementation) | +| Operation | Description | User (EParticipant) | ⚠️ User w/ EPosition (current beta) | Rebalancer (ERebalance) | User — own positions only (EPositionAdmin) | Governance (EGovernance) | Protocol Internal (EImplementation) | |---|---|---|---|---|---|---|---| | `createPosition` | Open a new position | ✅ | ✅ | | | | | | `depositToPosition` | Deposit collateral | ✅ | ✅ | | | | | From f9f728b44fa53849a2b92d3365b5424d6855435d Mon Sep 17 00:00:00 2001 From: Taras Maliarchuk Date: Fri, 6 Mar 2026 14:24:32 +0100 Subject: [PATCH 05/10] Add comprehensive entitlement/capability access-control test suite --- FlowActions | 2 +- README.md | 2 +- ...rsarial_recursive_withdraw_source_test.cdc | 2 +- .../tests/adversarial_type_spoofing_test.cdc | 2 +- cadence/tests/async_update_position_test.cdc | 2 +- cadence/tests/cap_test.cdc | 816 +++++++++++++++++- cadence/tests/insurance_swapper_test.cdc | 4 +- cadence/tests/liquidation_phase1_test.cdc | 6 +- .../tests/position_lifecycle_unhappy_test.cdc | 4 +- cadence/tests/test_helpers.cdc | 21 +- .../flow-alp/beta/publish_beta_cap.cdc | 18 - .../egovernance/add_supported_token.cdc | 38 + .../egovernance/collect_insurance.cdc | 23 + .../egovernance/collect_stability.cdc | 23 + .../set_deposit_limit_fraction.cdc | 26 + .../flow-alp/egovernance/set_dex.cdc | 24 + .../egovernance/set_insurance_rate.cdc | 26 + .../egovernance/set_interest_curve.cdc | 31 + .../egovernance/set_liquidation_params.cdc | 23 + .../flow-alp/egovernance/set_oracle.cdc | 25 + .../flow-alp/egovernance/set_pause_params.cdc | 23 + .../egovernance/set_stability_fee_rate.cdc | 26 + .../flow-alp/eimplementation/async_update.cdc | 22 + .../async_update_position.cdc | 0 .../eimplementation/regenerate_capacities.cdc | 20 + .../create_and_deposit_via_cap.cdc | 54 ++ .../create_and_deposit_via_storage.cdc} | 9 + .../create_position_via_published_cap.cdc | 49 ++ .../eposition/deposit_and_push_any.cdc | 33 + .../flow-alp/eposition/lock_any.cdc | 28 + .../flow-alp/eposition/rebalance_pool.cdc | 28 + .../eposition/withdraw_and_pull_any.cdc | 38 + .../flow-alp/eposition/withdraw_any.cdc | 33 + .../epositionadmin/add_remove_position.cdc | 52 ++ .../epositionadmin/borrow_authorized.cdc | 25 + .../flow-alp/epositionadmin/provide_sink.cdc | 30 + .../epositionadmin/provide_source.cdc | 26 + .../epositionadmin/set_max_health.cdc | 27 + .../epositionadmin/set_min_health.cdc | 27 + .../epositionadmin/set_target_health.cdc | 27 + .../erebalance/pool_rebalance_position.cdc | 23 + .../flow-alp/erebalance/rebalance_pool.cdc | 28 + .../manual_liquidation_chosen_vault.cdc | 0 .../remove_insurance_swapper.cdc | 0 .../set_insurance_swapper_mock.cdc | 0 .../set_pool_paused.cdc | 6 +- .../withdraw_from_position.cdc | 0 .../01_negative_no_eparticipant_fail.cdc | 28 - .../pool-management/04_create_position.cdc | 38 - .../pool-management/05_negative_cap.cdc | 20 - .../claim_beta_cap.cdc} | 0 .../create_and_store_pool.cdc | 0 .../grant_beta_cap.cdc} | 0 .../flow-alp/setup/grant_egovernance_cap.cdc | 23 + .../flow-alp/setup/grant_eparticipant_cap.cdc | 23 + .../flow-alp/setup/grant_eposition_cap.cdc | 25 + .../flow-alp/setup/grant_erebalance_cap.cdc | 23 + docs/security-permission-matrix.md | 14 +- 58 files changed, 1789 insertions(+), 157 deletions(-) delete mode 100644 cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/add_supported_token.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/collect_insurance.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/collect_stability.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_deposit_limit_fraction.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_dex.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_insurance_rate.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_interest_curve.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_liquidation_params.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_oracle.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_pause_params.cdc create mode 100644 cadence/tests/transactions/flow-alp/egovernance/set_stability_fee_rate.cdc create mode 100644 cadence/tests/transactions/flow-alp/eimplementation/async_update.cdc rename cadence/tests/transactions/flow-alp/{pool-management => eimplementation}/async_update_position.cdc (100%) create mode 100644 cadence/tests/transactions/flow-alp/eimplementation/regenerate_capacities.cdc create mode 100644 cadence/tests/transactions/flow-alp/eparticipant/create_and_deposit_via_cap.cdc rename cadence/tests/transactions/flow-alp/{pool-management/02_positive_with_eparticipant_pass.cdc => eparticipant/create_and_deposit_via_storage.cdc} (74%) create mode 100644 cadence/tests/transactions/flow-alp/eparticipant/create_position_via_published_cap.cdc create mode 100644 cadence/tests/transactions/flow-alp/eposition/deposit_and_push_any.cdc create mode 100644 cadence/tests/transactions/flow-alp/eposition/lock_any.cdc create mode 100644 cadence/tests/transactions/flow-alp/eposition/rebalance_pool.cdc create mode 100644 cadence/tests/transactions/flow-alp/eposition/withdraw_and_pull_any.cdc create mode 100644 cadence/tests/transactions/flow-alp/eposition/withdraw_any.cdc create mode 100644 cadence/tests/transactions/flow-alp/epositionadmin/add_remove_position.cdc create mode 100644 cadence/tests/transactions/flow-alp/epositionadmin/borrow_authorized.cdc create mode 100644 cadence/tests/transactions/flow-alp/epositionadmin/provide_sink.cdc create mode 100644 cadence/tests/transactions/flow-alp/epositionadmin/provide_source.cdc create mode 100644 cadence/tests/transactions/flow-alp/epositionadmin/set_max_health.cdc create mode 100644 cadence/tests/transactions/flow-alp/epositionadmin/set_min_health.cdc create mode 100644 cadence/tests/transactions/flow-alp/epositionadmin/set_target_health.cdc create mode 100644 cadence/tests/transactions/flow-alp/erebalance/pool_rebalance_position.cdc create mode 100644 cadence/tests/transactions/flow-alp/erebalance/rebalance_pool.cdc rename cadence/tests/transactions/flow-alp/{pool-management => helpers}/manual_liquidation_chosen_vault.cdc (100%) rename cadence/tests/transactions/flow-alp/{pool-governance => helpers}/remove_insurance_swapper.cdc (100%) rename cadence/tests/transactions/flow-alp/{pool-governance => helpers}/set_insurance_swapper_mock.cdc (100%) rename cadence/tests/transactions/flow-alp/{pool-governance => helpers}/set_pool_paused.cdc (69%) rename cadence/tests/transactions/flow-alp/{pool-management => helpers}/withdraw_from_position.cdc (100%) delete mode 100644 cadence/tests/transactions/flow-alp/pool-management/01_negative_no_eparticipant_fail.cdc delete mode 100644 cadence/tests/transactions/flow-alp/pool-management/04_create_position.cdc delete mode 100644 cadence/tests/transactions/flow-alp/pool-management/05_negative_cap.cdc rename cadence/tests/transactions/flow-alp/{beta/claim_and_save_beta_cap.cdc => setup/claim_beta_cap.cdc} (100%) rename cadence/tests/transactions/flow-alp/{pool-factory => setup}/create_and_store_pool.cdc (100%) rename cadence/tests/transactions/flow-alp/{pool-management/03_grant_beta.cdc => setup/grant_beta_cap.cdc} (100%) create mode 100644 cadence/tests/transactions/flow-alp/setup/grant_egovernance_cap.cdc create mode 100644 cadence/tests/transactions/flow-alp/setup/grant_eparticipant_cap.cdc create mode 100644 cadence/tests/transactions/flow-alp/setup/grant_eposition_cap.cdc create mode 100644 cadence/tests/transactions/flow-alp/setup/grant_erebalance_cap.cdc diff --git a/FlowActions b/FlowActions index 6769d4c9..f24bb66e 160000 --- a/FlowActions +++ b/FlowActions @@ -1 +1 @@ -Subproject commit 6769d4c9f9ded4a5b4404d8c982300e84ccef532 +Subproject commit f24bb66e440224921d8e521b596790d01a0b4580 diff --git a/README.md b/README.md index 4714564b..d37f933a 100644 --- a/README.md +++ b/README.md @@ -179,7 +179,7 @@ FlowALP/ - `FungibleToken.Vault`: Standard token operations - `DeFiActions.Sink/Source`: DeFi protocol composability -- Entitlements: `FlowALPv0.EParticipant`, `FlowALPv0.EPosition`, `FlowALPv0.EGovernance`, `FlowALPv0.ERebalance` +- Entitlements: `FlowALPModels.EParticipant`, `FlowALPModels.EPosition`, `FlowALPModels.EGovernance`, `FlowALPModels.ERebalance` ## 🛠️ Development diff --git a/cadence/tests/adversarial_recursive_withdraw_source_test.cdc b/cadence/tests/adversarial_recursive_withdraw_source_test.cdc index 3ca84da8..4a6ddd6e 100644 --- a/cadence/tests/adversarial_recursive_withdraw_source_test.cdc +++ b/cadence/tests/adversarial_recursive_withdraw_source_test.cdc @@ -120,7 +120,7 @@ fun testRecursiveWithdrawSource() { // In this test, the topUpSource behavior is adversarial: it attempts to re-enter // the pool during the pull/deposit flow. We expect the transaction to fail. let withdrawRes = executeTransaction( - "./transactions/flow-alp/pool-management/withdraw_from_position.cdc", + "./transactions/flow-alp/helpers/withdraw_from_position.cdc", [positionID, flowTokenIdentifier, 1500.0, true], // pullFromTopUpSource: true userAccount ) diff --git a/cadence/tests/adversarial_type_spoofing_test.cdc b/cadence/tests/adversarial_type_spoofing_test.cdc index 1edb2614..4e12c4db 100644 --- a/cadence/tests/adversarial_type_spoofing_test.cdc +++ b/cadence/tests/adversarial_type_spoofing_test.cdc @@ -59,7 +59,7 @@ fun testMaliciousSource() { // withdraw 1337 Flow from the position let withdrawRes = executeTransaction( - "./transactions/flow-alp/pool-management/withdraw_from_position.cdc", + "./transactions/flow-alp/helpers/withdraw_from_position.cdc", [1 as UInt64, flowTokenIdentifier, 1337.0, true], hackerAccount ) diff --git a/cadence/tests/async_update_position_test.cdc b/cadence/tests/async_update_position_test.cdc index f6dc85ae..a7001255 100644 --- a/cadence/tests/async_update_position_test.cdc +++ b/cadence/tests/async_update_position_test.cdc @@ -41,7 +41,7 @@ fun testUpdatePosition() { depositToPosition(signer: user, positionID: 0, amount: 600.0, vaultStoragePath: FLOW_VAULT_STORAGE_PATH, pushToDrawDownSink: false) let updatePositionRes = _executeTransaction( - "./transactions/flow-alp/pool-management/async_update_position.cdc", + "./transactions/flow-alp/eimplementation/async_update_position.cdc", [ 0 as UInt64 ], PROTOCOL_ACCOUNT ) diff --git a/cadence/tests/cap_test.cdc b/cadence/tests/cap_test.cdc index d2df3573..4a1c7fe6 100644 --- a/cadence/tests/cap_test.cdc +++ b/cadence/tests/cap_test.cdc @@ -4,78 +4,830 @@ import BlockchainHelpers import "MOET" import "test_helpers.cdc" -// ----------------------------------------------------------------------------- -// Pool Creation Workflow Test -// ----------------------------------------------------------------------------- -// Validates that a pool can be created and that essential invariants hold. -// ----------------------------------------------------------------------------- +// ============================================================================= +// Security Permission Tests +// ============================================================================= +// +// This file validates the Actor Capability Matrix defined in: +// docs/security-permission-matrix.md +// +// One section per entitlement. Each section uses the production actor for that +// entitlement and covers ALL matrix operations for that entitlement. +// Role accounts are configured in setup() with the exact access they would +// receive in production. +// +// eParticipantUser — Capability +// Published via the FIXED publish_beta_cap.cdc. +// Cap stored at FlowALPv0.PoolCapStoragePath. +// +// ePositionUser — Capability +// EPosition-only capability; can perform pool-level position +// ops on any position by ID. No EParticipant. +// Cap stored at FlowALPv0.PoolCapStoragePath. +// +// eParticipantPositionUser — Capability over-grant +// Current (unfixed) beta cap — grants EPosition unnecessarily. +// Cap stored at FlowALPv0.PoolCapStoragePath. +// +// eRebalanceUser — Capability +// Narrowly-scoped cap for rebalancer contracts. +// Cap stored at FlowALPv0.PoolCapStoragePath. +// +// ePositionAdminUser — No Pool capability; has PositionManager in own storage. +// EPositionAdmin access is via storage ownership — cannot +// be delegated as a capability. +// +// eGovernanceUser — Capability +// Granted by PROTOCOL_ACCOUNT via grant_egovernance_cap.cdc. +// Cap stored at FlowALPv0.PoolCapStoragePath. +// +// PROTOCOL_ACCOUNT — Pool owner; exercises EImplementation directly via storage borrow. +// +// Negative tests: +// Cadence entitlements for Pool capabilities (EParticipant, EPosition, ERebalance, +// EGovernance, EImplementation) are enforced by the type checker at check time. +// A transaction that calls an entitlement-gated method without holding that entitlement +// is rejected before it can be submitted — no runtime negative test is needed. +// +// Runtime negative tests are present where access is enforced at runtime, not statically: +// 1. Cap not issued — a user without a cap at a given storage path gets nil from borrow +// and panics. Representative case: testEPositionAdmin_SetTargetHealth_Neg +// (ePositionUser has no PositionManager). +// 2. Ownership check — a user with a PositionManager requests a pid not in their manager. +// Representative case: testEPositionAdmin_BorrowUnauthorized_Fails. +// +// ============================================================================= + + +// Position created for PROTOCOL_ACCOUNT in setup — used as target for EPosition tests. +// Pool.nextPositionID starts at 0; the first createPosition call produces pid=0. +access(all) var setupPid: UInt64 = 0 +access(all) var ePositionAdminPid: UInt64 = 0 access(all) var snapshot: UInt64 = 0 +// Role accounts +access(all) var eParticipantUser = Test.createAccount() +access(all) var ePositionUser = Test.createAccount() +access(all) var eParticipantPositionUser = Test.createAccount() +access(all) var eRebalanceUser = Test.createAccount() +access(all) var ePositionAdminUser = Test.createAccount() +access(all) var eGovernanceUser = Test.createAccount() + +access(all) +fun safeReset() { + if getCurrentBlockHeight() > snapshot { + Test.reset(to: snapshot) + } +} + +/// Execute a 2-authorizer transaction (e.g. admin + grantee for capability setup). +access(all) +fun _execute2Signers( + _ path: String, + _ args: [AnyStruct], + _ s1: Test.TestAccount, + _ s2: Test.TestAccount +): Test.TransactionResult { + let signers = s1.address == s2.address ? [s1] : [s1, s2] + return Test.executeTransaction(Test.Transaction( + code: Test.readFile(path), + authorizers: [s1.address, s2.address], + signers: signers, + arguments: args + )) +} + // ----------------------------------------------------------------------------- // SETUP // ----------------------------------------------------------------------------- + access(all) fun setup() { deployContracts() + // Create pool with MOET as the default token, oracle price = $1 createAndStorePool(signer: PROTOCOL_ACCOUNT, defaultTokenIdentifier: MOET_TOKEN_IDENTIFIER, beFailed: false) + setMockOraclePrice(signer: PROTOCOL_ACCOUNT, forTokenIdentifier: MOET_TOKEN_IDENTIFIER, price: 1.0) + mintMoet(signer: PROTOCOL_ACCOUNT, to: PROTOCOL_ACCOUNT.address, amount: 1_000.0, beFailed: false) + // Verify pool invariants before setting up role accounts let exists = poolExists(address: PROTOCOL_ACCOUNT.address) Test.assert(exists) - - // Reserve balance should be zero for default token let reserveBal = getReserveBalance(vaultIdentifier: MOET_TOKEN_IDENTIFIER) Test.assertEqual(0.0, reserveBal) + // Create setupPid=0 owned by PROTOCOL_ACCOUNT. + // Used as target in EPosition/ERebalance tests. + createPosition( + admin: PROTOCOL_ACCOUNT, + signer: PROTOCOL_ACCOUNT, + amount: 10.0, + vaultStoragePath: MOET.VaultStoragePath, + pushToDrawDownSink: false + ) + + setupPid = getLastPositonId() + + // ───────────────────────────────────────────────────────────────────────── + // EParticipant user — EParticipant-ONLY capability (fixed beta cap) + // ───────────────────────────────────────────────────────────────────────── + setupMoetVault(eParticipantUser, beFailed: false) + mintMoet(signer: PROTOCOL_ACCOUNT, to: eParticipantUser.address, amount: 100.0, beFailed: false) + Test.expect( + _execute2Signers( + "../tests/transactions/flow-alp/setup/grant_eparticipant_cap.cdc", + [], + PROTOCOL_ACCOUNT, + eParticipantUser + ), + Test.beSucceeded() + ) + + // ───────────────────────────────────────────────────────────────────────── + // EPosition user — EPosition-ONLY capability (no EParticipant) + // ───────────────────────────────────────────────────────────────────────── + setupMoetVault(ePositionUser, beFailed: false) + mintMoet(signer: PROTOCOL_ACCOUNT, to: ePositionUser.address, amount: 100.0, beFailed: false) + Test.expect( + _execute2Signers( + "../tests/transactions/flow-alp/setup/grant_eposition_cap.cdc", + [], + PROTOCOL_ACCOUNT, + ePositionUser + ), + Test.beSucceeded() + ) + + // ───────────────────────────────────────────────────────────────────────── + // EParticipantPosition user — EParticipant+EPosition capability (current over-grant) + // ───────────────────────────────────────────────────────────────────────── + setupMoetVault(eParticipantPositionUser, beFailed: false) + mintMoet(signer: PROTOCOL_ACCOUNT, to: eParticipantPositionUser.address, amount: 100.0, beFailed: false) + grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, eParticipantPositionUser) + + // ───────────────────────────────────────────────────────────────────────── + // ERebalance user — ERebalance-only capability (rebalancer simulation) + // ───────────────────────────────────────────────────────────────────────── + Test.expect( + _execute2Signers( + "../tests/transactions/flow-alp/setup/grant_erebalance_cap.cdc", + [], + PROTOCOL_ACCOUNT, + eRebalanceUser + ), + Test.beSucceeded() + ) + + // ───────────────────────────────────────────────────────────────────────── + // EPositionAdmin user — has PositionManager in own storage (pid=1) + // EPositionAdmin access comes from storage ownership, not a delegated cap. + // ───────────────────────────────────────────────────────────────────────── + setupMoetVault(ePositionAdminUser, beFailed: false) + mintMoet(signer: PROTOCOL_ACCOUNT, to: ePositionAdminUser.address, amount: 100.0, beFailed: false) + createPosition( + admin: PROTOCOL_ACCOUNT, + signer: ePositionAdminUser, + amount: 10.0, + vaultStoragePath: MOET.VaultStoragePath, + pushToDrawDownSink: false + ) + ePositionAdminPid = getLastPositonId() + + // ───────────────────────────────────────────────────────────────────────── + // EGovernance user — EGovernance capability delegated from PROTOCOL_ACCOUNT + // ───────────────────────────────────────────────────────────────────────── + Test.expect( + _execute2Signers( + "../tests/transactions/flow-alp/setup/grant_egovernance_cap.cdc", + [], + PROTOCOL_ACCOUNT, + eGovernanceUser + ), + Test.beSucceeded() + ) + snapshot = getCurrentBlockHeight() } -// ----------------------------------------------------------------------------- -// TEST CASES -// ----------------------------------------------------------------------------- +// ============================================================================= +// Publish / Claim flow — capability grant mechanism +// ============================================================================= +/// publish → claim → create position round-trip using production beta transactions. access(all) -fun testPositionCreationFail() { +fun testPublishClaimCap() { + safeReset() + + let publishCapResult = _executeTransaction( + "../transactions/flow-alp/beta/publish_beta_cap.cdc", + [PROTOCOL_ACCOUNT.address], + PROTOCOL_ACCOUNT + ) + Test.expect(publishCapResult, Test.beSucceeded()) - let txResult = _executeTransaction( - "../tests/transactions/flow-alp/pool-management/01_negative_no_eparticipant_fail.cdc", + let claimCapResult = _executeTransaction( + "../transactions/flow-alp/beta/claim_and_save_beta_cap.cdc", + [PROTOCOL_ACCOUNT.address], + PROTOCOL_ACCOUNT + ) + Test.expect(claimCapResult, Test.beSucceeded()) + + let createPositionResult = _executeTransaction( + "../tests/transactions/flow-alp/eparticipant/create_position_via_published_cap.cdc", [], PROTOCOL_ACCOUNT ) - Test.expect(txResult, Test.beFailed()) + Test.expect(createPositionResult, Test.beSucceeded()) +} + +// ============================================================================= +// EParticipant — fixed beta capability (EParticipant only) +// ============================================================================= +// +// Actor: eParticipantUser — Capability +// Matrix rows: createPosition, depositToPosition + +/// EParticipant cap allows createPosition and depositToPosition. +access(all) +fun testEParticipant_CreateAndDeposit() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eparticipant/create_and_deposit_via_cap.cdc", + [], + eParticipantUser + ) + Test.expect(result, Test.beSucceeded()) +} + +// ============================================================================= +// EParticipant+EPosition — over-grant (current beta cap via publish_beta_cap.cdc) +// ============================================================================= +// +// Actor: eParticipantPositionUser — Capability +// Issued by publish_beta_cap.cdc and stored at FlowALPv0.PoolCapStoragePath. +// This is the CURRENT (unfixed) beta cap. EPosition is NOT needed for normal +// user actions; its presence lets this actor perform pool-level position ops +// on ANY position, including positions owned by other accounts. +// +// Matrix rows: createPosition (EParticipant), depositToPosition (EParticipant), +// withdraw [OVERGRANT], withdrawAndPull [OVERGRANT], depositAndPush [OVERGRANT], +// lockPosition [OVERGRANT], unlockPosition [OVERGRANT], rebalancePosition [OVERGRANT] +// +// The [OVERGRANT] rows confirm the security issue: a normal beta user can operate on +// positions they do not own (setupPid is owned by PROTOCOL_ACCOUNT). + +/// Over-granted beta cap still allows EParticipant operations (createPosition, depositToPosition). +access(all) +fun testEParticipantPosition_CreateAndDeposit() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eparticipant/create_and_deposit_via_cap.cdc", + [], + eParticipantPositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// Over-granted beta cap allows Pool.withdraw on ANY position — including +/// setupPid owned by PROTOCOL_ACCOUNT. +access(all) +fun testEParticipantPosition_WithdrawAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/withdraw_any.cdc", + [setupPid, 1.0], + eParticipantPositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// Over-granted beta cap allows Pool.withdrawAndPull on ANY position — including +/// positions owned by other accounts. +access(all) +fun testEParticipantPosition_WithdrawAndPullAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/withdraw_and_pull_any.cdc", + [setupPid, 1.0], + eParticipantPositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// Over-granted beta cap allows Pool.depositAndPush on ANY position — including +/// positions owned by other accounts. +access(all) +fun testEParticipantPosition_DepositAndPushAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/deposit_and_push_any.cdc", + [setupPid, 1.0], + eParticipantPositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// Over-granted beta cap allows Pool.lockPosition and Pool.unlockPosition on ANY position — +/// including positions owned by other accounts. +access(all) +fun testEParticipantPosition_LockUnlockAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/lock_any.cdc", + [setupPid], + eParticipantPositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// Over-granted beta cap allows Pool.rebalancePosition on any position. +access(all) +fun testEParticipantPosition_RebalancePosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/rebalance_pool.cdc", + [setupPid, true], + eParticipantPositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +// ============================================================================= +// EPosition — narrowly-scoped EPosition-only Pool capability +// ============================================================================= +// +// Actor: ePositionUser — Capability +// Matrix rows: withdraw, withdrawAndPull, depositAndPush, lockPosition, unlockPosition, +// rebalancePosition + +/// EPosition cap allows Pool.withdraw on ANY position by ID — including +/// setupPid owned by PROTOCOL_ACCOUNT. +access(all) +fun testEPosition_WithdrawAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/withdraw_any.cdc", + [setupPid, 1.0], + ePositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPosition cap allows Pool.withdrawAndPull on ANY position — including positions +/// owned by other accounts. +access(all) +fun testEPosition_WithdrawAndPullAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/withdraw_and_pull_any.cdc", + [setupPid, 1.0], + ePositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPosition cap allows Pool.depositAndPush on ANY position — including positions +/// owned by other accounts. +access(all) +fun testEPosition_DepositAndPushAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/deposit_and_push_any.cdc", + [setupPid, 1.0], + ePositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPosition cap allows Pool.lockPosition and Pool.unlockPosition on ANY position — +/// including positions owned by other accounts. +access(all) +fun testEPosition_LockUnlockAnyPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/lock_any.cdc", + [setupPid], + ePositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPosition cap allows Pool.rebalancePosition. +access(all) +fun testEPosition_RebalancePosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eposition/rebalance_pool.cdc", + [setupPid, true], + ePositionUser + ) + Test.expect(result, Test.beSucceeded()) +} + +// ============================================================================= +// ERebalance — narrowly-scoped rebalancer capability +// ============================================================================= +// +// Actor: eRebalanceUser — Capability @ PoolCapStoragePath +// Matrix rows: rebalancePosition, rebalance (Position) +// Both tested via pool.rebalancePosition(); Position.rebalance() delegates to same call. +// Contract fix: Position.pool changed to Capability +// so the internal call chain works for ERebalance callers. + +/// ERebalance cap allows Pool.rebalancePosition. +access(all) +fun testERebalance_RebalancePosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/erebalance/rebalance_pool.cdc", + [setupPid, true], + eRebalanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// ERebalance cap exercises Pool.rebalancePosition, which is the target of Position.rebalance(). +/// The contract fix (EPosition | ERebalance on Position.pool) ensures the internal call chain +/// for Position.rebalance() works under ERebalance. Both matrix rows share this entry point. +access(all) +fun testERebalance_PositionRebalance() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/erebalance/rebalance_pool.cdc", + [setupPid, true], + eRebalanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +// ============================================================================= +// EPositionAdmin — storage ownership of PositionManager (not a capability) +// ============================================================================= +// +// Actor: ePositionAdminUser — has PositionManager in own storage (cannot be delegated). +// Matrix rows: setTargetHealth, setMinHealth, setMaxHealth, provideSink, provideSource, +// addPosition (Manager), removePosition (Manager), borrowAuthorizedPosition +// +// Note: testEPositionAdmin_AddRemovePosition uses PROTOCOL_ACCOUNT because +// add_remove_position.cdc creates a fresh position via pool storage (EParticipant), +// which ePositionAdminUser does not hold. The EPositionAdmin entitlement is still +// tested via the PositionManager borrow inside the transaction. + +/// EPositionAdmin allows Position.setTargetHealth (via PositionManager.borrowAuthorizedPosition). +access(all) +fun testEPositionAdmin_SetTargetHealth() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/set_target_health.cdc", + [ePositionAdminPid, TARGET_HEALTH], + ePositionAdminUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPositionAdmin allows Position.setMinHealth (via PositionManager.borrowAuthorizedPosition). +access(all) +fun testEPositionAdmin_SetMinHealth() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/set_min_health.cdc", + [ePositionAdminPid, MIN_HEALTH], + ePositionAdminUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPositionAdmin allows Position.setMaxHealth (via PositionManager.borrowAuthorizedPosition). +access(all) +fun testEPositionAdmin_SetMaxHealth() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/set_max_health.cdc", + [ePositionAdminPid, MAX_HEALTH], + ePositionAdminUser + ) + Test.expect(result, Test.beSucceeded()) } +/// EPositionAdmin allows Position.provideSink. +/// Sets a DummySink (accepts MOET) then clears it with nil. access(all) -fun testPositionCreationSuccess() { - Test.reset(to: snapshot) +fun testEPositionAdmin_ProvideSink() { + safeReset() - let txResult = _executeTransaction( - "../tests/transactions/flow-alp/pool-management/02_positive_with_eparticipant_pass.cdc", + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/provide_sink.cdc", + [ePositionAdminPid], + ePositionAdminUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPositionAdmin allows Position.provideSource. +/// Calls provideSource(nil) to clear any existing source — always valid. +access(all) +fun testEPositionAdmin_ProvideSource() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/provide_source.cdc", + [ePositionAdminPid], + ePositionAdminUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPositionAdmin allows PositionManager.addPosition and PositionManager.removePosition. +/// Creates a fresh position, adds it to the manager, removes it, and destroys it. +/// Uses PROTOCOL_ACCOUNT because the transaction needs pool storage access to create +/// the position (EParticipant) — which ePositionAdminUser does not hold. +access(all) +fun testEPositionAdmin_AddRemovePosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/add_remove_position.cdc", [], PROTOCOL_ACCOUNT ) + Test.expect(result, Test.beSucceeded()) +} + +/// EPositionAdmin allows PositionManager.borrowAuthorizedPosition. +access(all) +fun testEPositionAdmin_BorrowAuthorizedPosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/borrow_authorized.cdc", + [ePositionAdminPid], + ePositionAdminUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// Negative: borrowAuthorizedPosition panics when the requested pid is not in the signer's +/// PositionManager. setupPid is owned by PROTOCOL_ACCOUNT, not ePositionAdminUser. +/// This is the only runtime-enforced access denial in this file — all other entitlements +/// are enforced statically by the Cadence type checker at check time. +access(all) +fun testEPositionAdmin_BorrowUnauthorizedPosition_Fails() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/epositionadmin/borrow_authorized.cdc", + [setupPid], + ePositionAdminUser + ) + Test.expect(result, Test.beFailed()) +} - Test.expect(txResult, Test.beSucceeded()) -} +// ============================================================================= +// EGovernance — capability-delegated governance access +// ============================================================================= +// +// Actor: eGovernanceUser — Capability +// Matrix rows: pausePool/unpausePool, addSupportedToken, setInterestCurve, setInsuranceRate, +// setStabilityFeeRate, setLiquidationParams, setPauseParams, setDepositLimitFraction, +// collectInsurance, collectStability, setDEX, setPriceOracle +// +// Note: withdrawStabilityFund (EGovernance) requires an active stability fund +// (non-zero debit balance + elapsed time + non-zero fee rate) and is therefore +// covered by the dedicated withdraw_stability_funds_test.cdc. +/// EGovernance cap allows Pool.pausePool and Pool.unpausePool. access(all) -fun testNegativeCap() { - Test.reset(to: snapshot) +fun testEGovernance_PauseUnpause() { + safeReset() - let negativeResult = _executeTransaction("../tests/transactions/flow-alp/pool-management/05_negative_cap.cdc", [], NON_ADMIN_ACCOUNT) - Test.expect(negativeResult, Test.beFailed()) + let pauseResult = _executeTransaction( + "../tests/transactions/flow-alp/helpers/set_pool_paused.cdc", + [true], + eGovernanceUser + ) + Test.expect(pauseResult, Test.beSucceeded()) + + let unpauseResult = _executeTransaction( + "../tests/transactions/flow-alp/helpers/set_pool_paused.cdc", + [false], + eGovernanceUser + ) + Test.expect(unpauseResult, Test.beSucceeded()) } +/// EGovernance cap allows Pool.addSupportedToken. +/// FlowToken is not added in setup, so this exercises a fresh token addition. access(all) -fun testPublishClaimCap() { - Test.reset(to: snapshot) - - let publishCapResult = _executeTransaction("../transactions/flow-alp/beta/publish_beta_cap.cdc", [PROTOCOL_ACCOUNT.address], PROTOCOL_ACCOUNT) - Test.expect(publishCapResult, Test.beSucceeded()) +fun testEGovernance_AddSupportedToken() { + safeReset() - let claimCapResult = _executeTransaction("../transactions/flow-alp/beta/claim_and_save_beta_cap.cdc", [PROTOCOL_ACCOUNT.address], PROTOCOL_ACCOUNT) - Test.expect(claimCapResult, Test.beSucceeded()) + // Oracle price for FlowToken is needed before using it as collateral/borrow, + // but adding a token to the pool does not require a price. + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/add_supported_token.cdc", + [FLOW_TOKEN_IDENTIFIER, 0.8, 0.8, 1_000_000.0, 1_000_000.0], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setInterestCurve. +access(all) +fun testEGovernance_SetInterestCurve() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_interest_curve.cdc", + [MOET_TOKEN_IDENTIFIER, 0.05 as UFix128], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setInsuranceRate. +/// Uses rate=0.0 because a non-zero rate requires an insurance swapper to be configured; +/// a zero rate still exercises the EGovernance entitlement check without that prerequisite. +access(all) +fun testEGovernance_SetInsuranceRate() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_insurance_rate.cdc", + [MOET_TOKEN_IDENTIFIER, 0.0], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setStabilityFeeRate. +access(all) +fun testEGovernance_SetStabilityFeeRate() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_stability_fee_rate.cdc", + [MOET_TOKEN_IDENTIFIER, 0.05], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setLiquidationParams (via borrowConfig). +access(all) +fun testEGovernance_SetLiquidationParams() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_liquidation_params.cdc", + [1.05 as UFix128], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setPauseParams (via borrowConfig). +access(all) +fun testEGovernance_SetPauseParams() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_pause_params.cdc", + [300 as UInt64], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setDepositLimitFraction. +access(all) +fun testEGovernance_SetDepositLimitFraction() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_deposit_limit_fraction.cdc", + [MOET_TOKEN_IDENTIFIER, 0.10], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.collectInsurance. +/// No insurance has accrued (zero insurance rate), but the call itself is valid. +access(all) +fun testEGovernance_CollectInsurance() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/collect_insurance.cdc", + [MOET_TOKEN_IDENTIFIER], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.collectStability. +/// No stability fees have accrued (zero stability rate), but the call itself is valid. +access(all) +fun testEGovernance_CollectStability() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/collect_stability.cdc", + [MOET_TOKEN_IDENTIFIER], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setDEX (via borrowConfig). +/// Uses MockDexSwapper.SwapperProvider as the DEX implementation. +access(all) +fun testEGovernance_SetDEX() { + safeReset() - let createPositionResult = _executeTransaction("../tests/transactions/flow-alp/pool-management/04_create_position.cdc", [], PROTOCOL_ACCOUNT) + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_dex.cdc", + [], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EGovernance cap allows Pool.setPriceOracle. +/// Uses MockOracle.PriceOracle whose unitOfAccount matches the pool's default token (MOET). +access(all) +fun testEGovernance_SetPriceOracle() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/egovernance/set_oracle.cdc", + [], + eGovernanceUser + ) + Test.expect(result, Test.beSucceeded()) +} + +// ============================================================================= +// EImplementation — protocol internals (never issued externally) +// ============================================================================= +// +// Actor: PROTOCOL_ACCOUNT — pool owner; EImplementation via direct storage borrow. +// Matrix rows: asyncUpdate, asyncUpdatePosition, regenerateAllDepositCapacities + +/// EImplementation allows Pool.asyncUpdate. +/// The queue may be empty in tests; asyncUpdate is a no-op when the queue is empty. +access(all) +fun testEImplementation_AsyncUpdate() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eimplementation/async_update.cdc", + [], + PROTOCOL_ACCOUNT + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EImplementation allows Pool.asyncUpdatePosition. +access(all) +fun testEImplementation_AsyncUpdatePosition() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eimplementation/async_update_position.cdc", + [setupPid], + PROTOCOL_ACCOUNT + ) + Test.expect(result, Test.beSucceeded()) +} + +/// EImplementation allows Pool.regenerateAllDepositCapacities. +access(all) +fun testEImplementation_RegenerateAllDepositCapacities() { + safeReset() + + let result = _executeTransaction( + "../tests/transactions/flow-alp/eimplementation/regenerate_capacities.cdc", + [], + PROTOCOL_ACCOUNT + ) + Test.expect(result, Test.beSucceeded()) } diff --git a/cadence/tests/insurance_swapper_test.cdc b/cadence/tests/insurance_swapper_test.cdc index 8885b6a6..9bb0ab2a 100644 --- a/cadence/tests/insurance_swapper_test.cdc +++ b/cadence/tests/insurance_swapper_test.cdc @@ -182,7 +182,7 @@ access(all) fun test_setInsuranceSwapper_wrongOutputType_fails() { // try to set a swapper that doesn't output MOET (outputs FLOW_TOKEN_IDENTIFIER instead) let res = _executeTransaction( - "./transactions/flow-alp/pool-governance/set_insurance_swapper_mock.cdc", + "./transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc", [MOET_TOKEN_IDENTIFIER, 1.0, MOET_TOKEN_IDENTIFIER, FLOW_TOKEN_IDENTIFIER], PROTOCOL_ACCOUNT ) @@ -199,7 +199,7 @@ access(all) fun test_setInsuranceSwapper_wrongInputType_fails() { // try to set a swapper with wrong input type (FLOW_TOKEN_IDENTIFIER instead of MOET_TOKEN_IDENTIFIER) let res = _executeTransaction( - "./transactions/flow-alp/pool-governance/set_insurance_swapper_mock.cdc", + "./transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc", [MOET_TOKEN_IDENTIFIER, 1.0, FLOW_TOKEN_IDENTIFIER, MOET_TOKEN_IDENTIFIER], PROTOCOL_ACCOUNT ) diff --git a/cadence/tests/liquidation_phase1_test.cdc b/cadence/tests/liquidation_phase1_test.cdc index da88b3d8..fcc76b9b 100644 --- a/cadence/tests/liquidation_phase1_test.cdc +++ b/cadence/tests/liquidation_phase1_test.cdc @@ -511,7 +511,7 @@ fun testManualLiquidation_repaymentVaultCollateralType() { let repayAmount = debtBalance + 0.001 let seizeAmount = (repayAmount / newPrice) * 0.99 let liqRes = _executeTransaction( - "../tests/transactions/flow-alp/pool-management/manual_liquidation_chosen_vault.cdc", + "../tests/transactions/flow-alp/helpers/manual_liquidation_chosen_vault.cdc", [pid, Type<@MOET.Vault>().identifier, FLOW_TOKEN_IDENTIFIER, FLOW_TOKEN_IDENTIFIER, seizeAmount, repayAmount], liquidator ) @@ -567,7 +567,7 @@ fun testManualLiquidation_repaymentVaultTypeMismatch() { let repayAmount = debtBalance + 0.001 let seizeAmount = (repayAmount / newPrice) * 0.99 let liqRes = _executeTransaction( - "../tests/transactions/flow-alp/pool-management/manual_liquidation_chosen_vault.cdc", + "../tests/transactions/flow-alp/helpers/manual_liquidation_chosen_vault.cdc", [pid, Type<@MOET.Vault>().identifier, MOCK_YIELD_TOKEN_IDENTIFIER, FLOW_TOKEN_IDENTIFIER, seizeAmount, repayAmount], liquidator ) @@ -622,7 +622,7 @@ fun testManualLiquidation_unsupportedDebtType() { let repayAmount = debtBalance + 0.001 let seizeAmount = (repayAmount / newPrice) * 0.99 let liqRes = _executeTransaction( - "../tests/transactions/flow-alp/pool-management/manual_liquidation_chosen_vault.cdc", + "../tests/transactions/flow-alp/helpers/manual_liquidation_chosen_vault.cdc", [pid, MOCK_YIELD_TOKEN_IDENTIFIER, MOCK_YIELD_TOKEN_IDENTIFIER, FLOW_TOKEN_IDENTIFIER, seizeAmount, repayAmount], liquidator ) diff --git a/cadence/tests/position_lifecycle_unhappy_test.cdc b/cadence/tests/position_lifecycle_unhappy_test.cdc index bc56b949..8b8d84ed 100644 --- a/cadence/tests/position_lifecycle_unhappy_test.cdc +++ b/cadence/tests/position_lifecycle_unhappy_test.cdc @@ -40,12 +40,12 @@ fun testPositionLifecycleBelowMinimumDeposit() { setMinimumTokenBalancePerPosition(signer: PROTOCOL_ACCOUNT, tokenTypeIdentifier: FLOW_TOKEN_IDENTIFIER, minimum: minimum) // position id to use for tests - let positionId = 0 as UInt64 + let positionId: UInt64 = 0 // user prep let user = Test.createAccount() setupMoetVault(user, beFailed: false) - mintFlow(to: user, amount: 1_000.0) + Test.expect(mintFlow(to: user, amount: 1_000.0), Test.beSucceeded()) // Grant beta access to user so they can create positions grantBetaPoolParticipantAccess(PROTOCOL_ACCOUNT, user) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index e6ac2ed7..17989299 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -1,6 +1,7 @@ import Test import "FlowALPv0" import "FlowALPModels" +import "FlowALPEvents" import "MOET" /* --- Global test constants --- */ @@ -76,7 +77,7 @@ access(all) fun grantBetaPoolParticipantAccess(_ admin: Test.TestAccount, _ grantee: Test.TestAccount) { let signers = admin.address == grantee.address ? [admin] : [admin, grantee] let betaTxn = Test.Transaction( - code: Test.readFile("./transactions/flow-alp/pool-management/03_grant_beta.cdc"), + code: Test.readFile("./transactions/flow-alp/setup/grant_beta_cap.cdc"), authorizers: [admin.address, grantee.address], signers: signers, arguments: [] @@ -387,7 +388,7 @@ fun getLastStabilityCollectionTime(tokenTypeIdentifier: String): UFix64? { access(all) fun createAndStorePool(signer: Test.TestAccount, defaultTokenIdentifier: String, beFailed: Bool) { let createRes = _executeTransaction( - "transactions/flow-alp/pool-factory/create_and_store_pool.cdc", + "./transactions/flow-alp/setup/create_and_store_pool.cdc", [defaultTokenIdentifier], signer ) @@ -511,7 +512,7 @@ fun setPoolPauseState( pause: Bool ): Test.TransactionResult { return _executeTransaction( - "./transactions/flow-alp/pool-governance/set_pool_paused.cdc", + "./transactions/flow-alp/helpers/set_pool_paused.cdc", [pause], signer ) @@ -656,7 +657,7 @@ fun setInsuranceSwapper( priceRatio: UFix64, ): Test.TransactionResult { let res = _executeTransaction( - "./transactions/flow-alp/pool-governance/set_insurance_swapper_mock.cdc", + "./transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc", [ tokenTypeIdentifier, priceRatio, tokenTypeIdentifier, MOET_TOKEN_IDENTIFIER], signer ) @@ -669,7 +670,7 @@ fun removeInsuranceSwapper( tokenTypeIdentifier: String, ): Test.TransactionResult { let res = _executeTransaction( - "./transactions/flow-alp/pool-governance/remove_insurance_swapper.cdc", + "./transactions/flow-alp/helpers/remove_insurance_swapper.cdc", [ tokenTypeIdentifier], signer ) @@ -913,3 +914,13 @@ fun getCreditBalanceForType(details: FlowALPModels.PositionDetails, vaultType: T } return 0.0 } + +access(all) +fun getLastPositonId(): UInt64 { + var openEvents = Test.eventsOfType(Type()) + if openEvents.length > 0 { + let pid = (openEvents[openEvents.length - 1] as! FlowALPEvents.Opened).pid + return pid + } + return 0 +} diff --git a/cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc b/cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc deleted file mode 100644 index 22b0fe82..00000000 --- a/cadence/tests/transactions/flow-alp/beta/publish_beta_cap.cdc +++ /dev/null @@ -1,18 +0,0 @@ -import "FlowALPv0" -import "FlowALPModels" - -transaction(grantee: Address) { - - prepare(admin: auth(IssueStorageCapabilityController, PublishInboxCapability) &Account) { - let poolCap: Capability = - admin.capabilities.storage.issue< - auth(FlowALPModels.EParticipant, FlowALPModels.EPosition) &FlowALPv0.Pool - >(FlowALPv0.PoolStoragePath) - - assert(poolCap.check(), message: "Failed to issue beta capability") - - admin.inbox.publish(poolCap, name: "FlowALPv0BetaCap", recipient: grantee) - } -} - - diff --git a/cadence/tests/transactions/flow-alp/egovernance/add_supported_token.cdc b/cadence/tests/transactions/flow-alp/egovernance/add_supported_token.cdc new file mode 100644 index 00000000..dcad7589 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/add_supported_token.cdc @@ -0,0 +1,38 @@ +import "FlowALPv0" +import "FlowALPModels" +import "FlowALPInterestRates" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.addSupportedToken. +/// Adds a token with a zero-rate interest curve (0% APY). +transaction( + tokenTypeIdentifier: String, + collateralFactor: UFix64, + borrowFactor: UFix64, + depositRate: UFix64, + depositCapacityCap: UFix64 +) { + let tokenType: Type + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier \(tokenTypeIdentifier)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.addSupportedToken( + tokenType: self.tokenType, + collateralFactor: collateralFactor, + borrowFactor: borrowFactor, + interestCurve: FlowALPInterestRates.FixedCurve(yearlyRate: 0.0), + depositRate: depositRate, + depositCapacityCap: depositCapacityCap + ) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/collect_insurance.cdc b/cadence/tests/transactions/flow-alp/egovernance/collect_insurance.cdc new file mode 100644 index 00000000..fbcec5d0 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/collect_insurance.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.collectInsurance. +transaction(tokenTypeIdentifier: String) { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + let tokenType: Type + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier: \(tokenTypeIdentifier)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.collectInsurance(tokenType: self.tokenType) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/collect_stability.cdc b/cadence/tests/transactions/flow-alp/egovernance/collect_stability.cdc new file mode 100644 index 00000000..0e3a226a --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/collect_stability.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.collectStability. +transaction(tokenTypeIdentifier: String) { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + let tokenType: Type + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier: \(tokenTypeIdentifier)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.collectStability(tokenType: self.tokenType) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_deposit_limit_fraction.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_deposit_limit_fraction.cdc new file mode 100644 index 00000000..a50f0d32 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_deposit_limit_fraction.cdc @@ -0,0 +1,26 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.setDepositLimitFraction. +transaction( + tokenTypeIdentifier: String, + fraction: UFix64 +) { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + let tokenType: Type + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier \(tokenTypeIdentifier)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.setDepositLimitFraction(tokenType: self.tokenType, fraction: fraction) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_dex.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_dex.cdc new file mode 100644 index 00000000..6d476302 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_dex.cdc @@ -0,0 +1,24 @@ +import "DeFiActions" +import "FlowALPv0" +import "FlowALPModels" +import "MockDexSwapper" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.borrowConfig, +/// enabling the governance holder to set the DEX via the config. +/// Uses MockDexSwapper.SwapperProvider as the DEX implementation. +transaction { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.borrowConfig().setDex(MockDexSwapper.SwapperProvider()) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_insurance_rate.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_insurance_rate.cdc new file mode 100644 index 00000000..deffa279 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_insurance_rate.cdc @@ -0,0 +1,26 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.setInsuranceRate. +transaction( + tokenTypeIdentifier: String, + insuranceRate: UFix64 +) { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + let tokenType: Type + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier \(tokenTypeIdentifier)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.setInsuranceRate(tokenType: self.tokenType, insuranceRate: insuranceRate) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_interest_curve.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_interest_curve.cdc new file mode 100644 index 00000000..df35eacf --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_interest_curve.cdc @@ -0,0 +1,31 @@ +import "FlowALPv0" +import "FlowALPModels" +import "FlowALPInterestRates" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.setInterestCurve. +/// Sets a FixedCurve with the given yearly rate. +transaction( + tokenTypeIdentifier: String, + yearlyRate: UFix128 +) { + let tokenType: Type + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier \(tokenTypeIdentifier)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.setInterestCurve( + tokenType: self.tokenType, + interestCurve: FlowALPInterestRates.FixedCurve(yearlyRate: yearlyRate) + ) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_liquidation_params.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_liquidation_params.cdc new file mode 100644 index 00000000..1927cc7a --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_liquidation_params.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.borrowConfig, +/// enabling the governance holder to set the liquidation target health factor. +/// +/// @param targetHF: The target health factor for liquidations (must be > 1.0) +transaction(targetHF: UFix128) { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.borrowConfig().setLiquidationTargetHF(targetHF) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_oracle.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_oracle.cdc new file mode 100644 index 00000000..4ee39865 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_oracle.cdc @@ -0,0 +1,25 @@ +import "DeFiActions" +import "FlowALPv0" +import "FlowALPModels" +import "MockOracle" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.setPriceOracle. +/// Uses MockOracle.PriceOracle as the oracle implementation. +/// The MockOracle's unitOfAccount must match the pool's default token (MOET). +transaction { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + // MockOracle.PriceOracle uses MOET as unitOfAccount, matching the pool's default token + self.pool.setPriceOracle(MockOracle.PriceOracle()) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_pause_params.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_pause_params.cdc new file mode 100644 index 00000000..96055e0e --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_pause_params.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.borrowConfig, +/// enabling the governance holder to set the warmup period. +/// +/// @param warmupSec: Warm-up period in seconds before pause takes full effect +transaction(warmupSec: UInt64) { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.borrowConfig().setWarmupSec(warmupSec) + } +} diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_stability_fee_rate.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_stability_fee_rate.cdc new file mode 100644 index 00000000..06792229 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/egovernance/set_stability_fee_rate.cdc @@ -0,0 +1,26 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EGovernance) &Pool grants access to Pool.setStabilityFeeRate. +transaction( + tokenTypeIdentifier: String, + stabilityFeeRate: UFix64 +) { + let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool + let tokenType: Type + + prepare(signer: auth(BorrowValue) &Account) { + self.tokenType = CompositeType(tokenTypeIdentifier) + ?? panic("Invalid tokenTypeIdentifier \(tokenTypeIdentifier)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + } + + execute { + self.pool.setStabilityFeeRate(tokenType: self.tokenType, stabilityFeeRate: stabilityFeeRate) + } +} diff --git a/cadence/tests/transactions/flow-alp/eimplementation/async_update.cdc b/cadence/tests/transactions/flow-alp/eimplementation/async_update.cdc new file mode 100644 index 00000000..fa3017e3 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eimplementation/async_update.cdc @@ -0,0 +1,22 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION — DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EImplementation) &Pool grants access to Pool.asyncUpdate. +/// EImplementation is never issued as an external capability — only the account +/// that owns the Pool in storage can access it. The queue may be empty; asyncUpdate +/// is a no-op in that case. +transaction { + let pool: auth(FlowALPModels.EImplementation) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow( + from: FlowALPv0.PoolStoragePath + ) ?? panic("Could not borrow Pool with EImplementation entitlement") + } + + execute { + self.pool.asyncUpdate() + } +} diff --git a/cadence/tests/transactions/flow-alp/pool-management/async_update_position.cdc b/cadence/tests/transactions/flow-alp/eimplementation/async_update_position.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/pool-management/async_update_position.cdc rename to cadence/tests/transactions/flow-alp/eimplementation/async_update_position.cdc diff --git a/cadence/tests/transactions/flow-alp/eimplementation/regenerate_capacities.cdc b/cadence/tests/transactions/flow-alp/eimplementation/regenerate_capacities.cdc new file mode 100644 index 00000000..074f7cc9 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eimplementation/regenerate_capacities.cdc @@ -0,0 +1,20 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EImplementation) &Pool grants access to Pool.regenerateAllDepositCapacities. +/// regenerateAllDepositCapacities recalculates deposit capacity for all supported token types +/// based on the configured deposit rate. Safe to call at any time. +transaction { + let pool: auth(FlowALPModels.EImplementation) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) + ?? panic("Could not borrow Pool with EImplementation entitlement") + } + + execute { + self.pool.regenerateAllDepositCapacities() + } +} diff --git a/cadence/tests/transactions/flow-alp/eparticipant/create_and_deposit_via_cap.cdc b/cadence/tests/transactions/flow-alp/eparticipant/create_and_deposit_via_cap.cdc new file mode 100644 index 00000000..ef80a72d --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eparticipant/create_and_deposit_via_cap.cdc @@ -0,0 +1,54 @@ +import "FungibleToken" +import "FlowALPv0" +import "FlowALPModels" +import "MOET" +import "DummyConnectors" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that Capability (EParticipant-ONLY, fixed beta cap) grants: +/// Pool.createPosition +/// Pool.depositToPosition +/// +/// Uses the cap stored at FlowALPv0.PoolCapStoragePath. +/// +/// NOTE: All logic is in prepare because @Position resources cannot be stored as +/// transaction fields, and execute has no storage access. The prepare-only pattern +/// is correct by necessity for resource-creating transactions. +transaction { + prepare(signer: auth(BorrowValue, Storage) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("EParticipant-only capability not found") + + let pool = cap.borrow() ?? panic("Could not borrow Pool with EParticipant") + + let vault = signer.storage.borrow(from: MOET.VaultStoragePath) + ?? panic("No MOET vault") + + // Ensure PositionManager exists (plain borrow is sufficient for addPosition) + if signer.storage.borrow<&FlowALPv0.PositionManager>(from: FlowALPv0.PositionStoragePath) == nil { + let manager <- FlowALPv0.createPositionManager() + signer.storage.save(<-manager, to: FlowALPv0.PositionStoragePath) + } + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("No PositionManager") + + let funds <- vault.withdraw(amount: 5.0) + + // createPosition — requires EParticipant + let position <- pool.createPosition( + funds: <-funds, + issuanceSink: DummyConnectors.DummySink(), + repaymentSource: nil, + pushToDrawDownSink: false + ) + let pid = position.id + manager.addPosition(position: <-position) + + // depositToPosition — requires EParticipant + let moreFunds <- vault.withdraw(amount: 1.0) + pool.depositToPosition(pid: pid, from: <-moreFunds) + } +} diff --git a/cadence/tests/transactions/flow-alp/pool-management/02_positive_with_eparticipant_pass.cdc b/cadence/tests/transactions/flow-alp/eparticipant/create_and_deposit_via_storage.cdc similarity index 74% rename from cadence/tests/transactions/flow-alp/pool-management/02_positive_with_eparticipant_pass.cdc rename to cadence/tests/transactions/flow-alp/eparticipant/create_and_deposit_via_storage.cdc index 17033aa0..50c73783 100644 --- a/cadence/tests/transactions/flow-alp/pool-management/02_positive_with_eparticipant_pass.cdc +++ b/cadence/tests/transactions/flow-alp/eparticipant/create_and_deposit_via_storage.cdc @@ -6,6 +6,15 @@ import "FlowALPModels" import "MOET" import "DummyConnectors" +/// TEST TRANSACTION — DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EParticipant) &Pool (issued inline as a storage capability) grants: +/// Pool.createPosition +/// Pool.depositToPosition +/// +/// NOTE: All logic is in prepare because @Position resources cannot be stored as +/// transaction fields, and execute has no storage access. The prepare-only pattern +/// is correct by necessity for resource-creating transactions. transaction { prepare(admin: auth(BorrowValue, IssueStorageCapabilityController) &Account) { let minter = admin.storage.borrow<&MOET.Minter>(from: MOET.AdminStoragePath) diff --git a/cadence/tests/transactions/flow-alp/eparticipant/create_position_via_published_cap.cdc b/cadence/tests/transactions/flow-alp/eparticipant/create_position_via_published_cap.cdc new file mode 100644 index 00000000..677938db --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eparticipant/create_position_via_published_cap.cdc @@ -0,0 +1,49 @@ +import "FungibleToken" +import "FlowALPv0" +import "FlowALPModels" +import "MOET" +import "DummyConnectors" + +/// TEST TRANSACTION — DO NOT USE IN PRODUCTION +/// +/// Verifies that a Pool borrow with auth(EParticipant, EPosition) allows +/// Pool.createPosition and Pool.depositToPosition, creating a PositionManager +/// if one does not already exist. Used after the publish→claim beta cap flow. +/// +/// NOTE: All logic is in prepare because @Position resources cannot be stored as +/// transaction fields, and execute has no storage access. The prepare-only pattern +/// is correct by necessity for resource-creating transactions. +transaction { + prepare(admin: auth(BorrowValue, Storage) &Account) { + let pool = admin.storage.borrow(from: FlowALPv0.PoolStoragePath) + ?? panic("Could not borrow Pool with EParticipant+EPosition entitlement") + + let moetVault = admin.storage.borrow(from: MOET.VaultStoragePath) + ?? panic("Could not borrow MOET vault") + + // Ensure PositionManager exists + if admin.storage.borrow<&FlowALPv0.PositionManager>(from: FlowALPv0.PositionStoragePath) == nil { + let manager <- FlowALPv0.createPositionManager() + admin.storage.save(<-manager, to: FlowALPv0.PositionStoragePath) + } + + // Pool.createPosition — requires EParticipant + let funds <- moetVault.withdraw(amount: 1.0) + let position <- pool.createPosition( + funds: <-funds, + issuanceSink: DummyConnectors.DummySink(), + repaymentSource: nil, + pushToDrawDownSink: false + ) + + let pid = position.id + + // Add position to manager + let manager = admin.storage.borrow(from: FlowALPv0.PositionStoragePath)! + manager.addPosition(position: <-position) + + // Pool.depositToPosition — requires EParticipant + let moreFunds <- moetVault.withdraw(amount: 1.0) + pool.depositToPosition(pid: pid, from: <-moreFunds) + } +} diff --git a/cadence/tests/transactions/flow-alp/eposition/deposit_and_push_any.cdc b/cadence/tests/transactions/flow-alp/eposition/deposit_and_push_any.cdc new file mode 100644 index 00000000..d0cf2308 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eposition/deposit_and_push_any.cdc @@ -0,0 +1,33 @@ +import "FungibleToken" +import "FlowALPv0" +import "FlowALPModels" +import "MOET" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that Capability grants: +/// Pool.depositAndPush — on a position owned by ANOTHER user +/// +/// EPosition allows pool-level position operations on any position by ID, +/// regardless of which account owns that position. No EParticipant required. +/// +/// @param pid: Target position ID (owned by a different account) +/// @param amount: Amount of MOET to deposit +transaction(pid: UInt64, amount: UFix64) { + let pool: auth(FlowALPModels.EPosition) &FlowALPv0.Pool + let funds: @{FungibleToken.Vault} + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("EPosition capability not found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool with EPosition") + let vault = signer.storage.borrow(from: MOET.VaultStoragePath) + ?? panic("Could not borrow MOET vault with Withdraw entitlement") + self.funds <- vault.withdraw(amount: amount) + } + + execute { + self.pool.depositAndPush(pid: pid, from: <-self.funds, pushToDrawDownSink: false) + } +} diff --git a/cadence/tests/transactions/flow-alp/eposition/lock_any.cdc b/cadence/tests/transactions/flow-alp/eposition/lock_any.cdc new file mode 100644 index 00000000..685b2290 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eposition/lock_any.cdc @@ -0,0 +1,28 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that Capability grants: +/// Pool.lockPosition — on ANY position +/// Pool.unlockPosition — on ANY position +/// +/// EPosition allows pool-level position operations on any position by ID, +/// regardless of which account owns that position. No EParticipant required. +/// +/// @param pid: Target position ID (may belong to a different account) +transaction(pid: UInt64) { + let pool: auth(FlowALPModels.EPosition) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("EPosition capability not found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool with EPosition") + } + + execute { + self.pool.lockPosition(pid) + self.pool.unlockPosition(pid) + } +} diff --git a/cadence/tests/transactions/flow-alp/eposition/rebalance_pool.cdc b/cadence/tests/transactions/flow-alp/eposition/rebalance_pool.cdc new file mode 100644 index 00000000..8916d02f --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eposition/rebalance_pool.cdc @@ -0,0 +1,28 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that Capability grants access to +/// Pool.rebalancePosition via the EPosition entitlement. +/// +/// ePositionUser holds auth(EPosition) &Pool which satisfies the EPosition | ERebalance +/// requirement of Pool.rebalancePosition. +/// +/// @param pid: Position to rebalance +/// @param force: Whether to force rebalance regardless of health bounds +transaction(pid: UInt64, force: Bool) { + let pool: auth(FlowALPModels.EPosition) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("EPosition capability not found at PoolCapStoragePath") + self.pool = cap.borrow() ?? panic("Could not borrow Pool with EPosition") + } + + execute { + // Pool.rebalancePosition — requires EPosition | ERebalance; EPosition alone is sufficient + self.pool.rebalancePosition(pid: pid, force: force) + } +} diff --git a/cadence/tests/transactions/flow-alp/eposition/withdraw_and_pull_any.cdc b/cadence/tests/transactions/flow-alp/eposition/withdraw_and_pull_any.cdc new file mode 100644 index 00000000..524cb32b --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eposition/withdraw_and_pull_any.cdc @@ -0,0 +1,38 @@ +import "FungibleToken" +import "FlowALPv0" +import "FlowALPModels" +import "MOET" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that Capability grants: +/// Pool.withdrawAndPull — on a position owned by ANOTHER user +/// +/// EPosition allows pool-level position operations on any position by ID, +/// regardless of which account owns that position. No EParticipant required. +/// +/// @param pid: Target position ID (owned by a different account) +/// @param amount: Amount to withdraw +transaction(pid: UInt64, amount: UFix64) { + let pool: auth(FlowALPModels.EPosition) &FlowALPv0.Pool + let receiver: &{FungibleToken.Receiver} + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("EPosition capability not found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool with EPosition") + self.receiver = signer.storage.borrow<&{FungibleToken.Receiver}>(from: MOET.VaultStoragePath) + ?? panic("No MOET vault receiver") + } + + execute { + let vault <- self.pool.withdrawAndPull( + pid: pid, + type: Type<@MOET.Vault>(), + amount: amount, + pullFromTopUpSource: false + ) + self.receiver.deposit(from: <-vault) + } +} diff --git a/cadence/tests/transactions/flow-alp/eposition/withdraw_any.cdc b/cadence/tests/transactions/flow-alp/eposition/withdraw_any.cdc new file mode 100644 index 00000000..50fde3b0 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/eposition/withdraw_any.cdc @@ -0,0 +1,33 @@ +import "FungibleToken" +import "FlowALPv0" +import "FlowALPModels" +import "MOET" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that Capability grants: +/// Pool.withdraw — on a position owned by ANOTHER user +/// +/// EPosition allows pool-level position operations on any position by ID, +/// regardless of which account owns that position. No EParticipant required. +/// +/// @param pid: Target position ID (owned by a different account) +/// @param amount: Amount to withdraw +transaction(pid: UInt64, amount: UFix64) { + let pool: auth(FlowALPModels.EPosition) &FlowALPv0.Pool + let receiver: &{FungibleToken.Receiver} + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("EPosition capability not found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool with EPosition") + self.receiver = signer.storage.borrow<&{FungibleToken.Receiver}>(from: MOET.VaultStoragePath) + ?? panic("No MOET vault receiver") + } + + execute { + let vault <- self.pool.withdraw(pid: pid, amount: amount, type: Type<@MOET.Vault>()) + self.receiver.deposit(from: <-vault) + } +} diff --git a/cadence/tests/transactions/flow-alp/epositionadmin/add_remove_position.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/add_remove_position.cdc new file mode 100644 index 00000000..6cd9bbef --- /dev/null +++ b/cadence/tests/transactions/flow-alp/epositionadmin/add_remove_position.cdc @@ -0,0 +1,52 @@ +import "FungibleToken" +import "FlowALPv0" +import "FlowALPModels" +import "MOET" +import "DummyConnectors" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EPositionAdmin) &PositionManager grants access to: +/// - PositionManager.addPosition +/// - PositionManager.removePosition +/// +/// Creates a fresh position, adds it to the PositionManager, removes it, and destroys it. +/// This confirms both operations are accessible when the PositionManager is borrowed +/// with the EPositionAdmin entitlement. +/// +/// NOTE: All logic is in prepare because @Position resources cannot be stored as +/// transaction fields, and execute has no storage access. The prepare-only pattern +/// is correct by necessity for resource-creating/moving transactions. +transaction { + prepare(signer: auth(BorrowValue, Storage) &Account) { + // Create a fresh position (direct borrow since signer owns the pool) + let pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) + ?? panic("Could not borrow Pool with EParticipant entitlement") + + let moetVault = signer.storage.borrow(from: MOET.VaultStoragePath) + ?? panic("Could not borrow MOET vault") + let funds <- moetVault.withdraw(amount: 1.0) + let position <- pool.createPosition( + funds: <-funds, + issuanceSink: DummyConnectors.DummySink(), + repaymentSource: nil, + pushToDrawDownSink: false + ) + let newPid = position.id + + // Get PositionManager with EPositionAdmin entitlement + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("Could not borrow PositionManager with EPositionAdmin entitlement") + + // Test addPosition (requires EPositionAdmin on PositionManager) + manager.addPosition(position: <-position) + + // Test removePosition (requires EPositionAdmin on PositionManager) + let removed <- manager.removePosition(pid: newPid) + + // Verify correctness and clean up + assert(removed.id == newPid, message: "Removed position ID does not match created position ID") + destroy removed + } +} diff --git a/cadence/tests/transactions/flow-alp/epositionadmin/borrow_authorized.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/borrow_authorized.cdc new file mode 100644 index 00000000..242fecc3 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/epositionadmin/borrow_authorized.cdc @@ -0,0 +1,25 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION — DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EPositionAdmin) &PositionManager grants access to +/// PositionManager.borrowAuthorizedPosition, which returns an authorized +/// auth(EPositionAdmin) &Position reference. +/// +/// @param pid: The position ID to borrow an authorized reference for +transaction(pid: UInt64) { + let posRef: auth(FlowALPModels.EPositionAdmin) &FlowALPv0.Position + + prepare(signer: auth(BorrowValue) &Account) { + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("Could not borrow PositionManager with EPositionAdmin entitlement") + + self.posRef = manager.borrowAuthorizedPosition(pid: pid) + } + + execute { + assert(self.posRef.id == pid, message: "Borrowed position ID does not match requested pid") + } +} diff --git a/cadence/tests/transactions/flow-alp/epositionadmin/provide_sink.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/provide_sink.cdc new file mode 100644 index 00000000..c8914538 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/epositionadmin/provide_sink.cdc @@ -0,0 +1,30 @@ +import "DeFiActions" +import "FlowALPv0" +import "FlowALPModels" +import "DummyConnectors" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EPositionAdmin) &Position grants access to Position.provideSink. +/// Borrows the PositionManager with EPositionAdmin, gets an authorized Position reference, +/// and sets a DummySink as the draw-down sink (then clears it with nil). +/// +/// @param pid: The position ID whose sink should be configured +transaction(pid: UInt64) { + let position: auth(FlowALPModels.EPositionAdmin) &FlowALPv0.Position + + prepare(signer: auth(BorrowValue) &Account) { + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("Could not borrow PositionManager with EPositionAdmin entitlement") + + self.position = manager.borrowAuthorizedPosition(pid: pid) + } + + execute { + // Set a sink (DummySink accepts MOET, which is the pool's default token) + self.position.provideSink(sink: DummyConnectors.DummySink()) + // Clear it again to leave state clean + self.position.provideSink(sink: nil) + } +} diff --git a/cadence/tests/transactions/flow-alp/epositionadmin/provide_source.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/provide_source.cdc new file mode 100644 index 00000000..40806509 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/epositionadmin/provide_source.cdc @@ -0,0 +1,26 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(EPositionAdmin) &Position grants access to Position.provideSource. +/// Borrows the PositionManager with EPositionAdmin, gets an authorized Position reference, +/// and clears the top-up source (nil is always a valid argument). +/// +/// @param pid: The position ID whose top-up source should be configured +transaction(pid: UInt64) { + let position: auth(FlowALPModels.EPositionAdmin) &FlowALPv0.Position + + prepare(signer: auth(BorrowValue) &Account) { + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("Could not borrow PositionManager with EPositionAdmin entitlement") + + self.position = manager.borrowAuthorizedPosition(pid: pid) + } + + execute { + // Passing nil clears any existing top-up source — always a valid no-op + self.position.provideSource(source: nil) + } +} diff --git a/cadence/tests/transactions/flow-alp/epositionadmin/set_max_health.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/set_max_health.cdc new file mode 100644 index 00000000..7c95d0af --- /dev/null +++ b/cadence/tests/transactions/flow-alp/epositionadmin/set_max_health.cdc @@ -0,0 +1,27 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION — DO NOT USE IN PRODUCTION +/// +/// Verifies that storage ownership of PositionManager grants EPositionAdmin access to +/// PositionManager.borrowAuthorizedPosition and Position.setMaxHealth. +/// EPositionAdmin comes exclusively from owning the PositionManager in storage +/// and cannot be delegated as a capability. +/// +/// @param pid: Own position ID to configure +/// @param maxHealth: Maximum health factor before auto-repay is triggered +transaction(pid: UInt64, maxHealth: UFix64) { + let position: auth(FlowALPModels.EPositionAdmin) &FlowALPv0.Position + + prepare(signer: auth(BorrowValue) &Account) { + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("Could not borrow PositionManager with EPositionAdmin entitlement") + + self.position = manager.borrowAuthorizedPosition(pid: pid) + } + + execute { + self.position.setMaxHealth(maxHealth: maxHealth) + } +} diff --git a/cadence/tests/transactions/flow-alp/epositionadmin/set_min_health.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/set_min_health.cdc new file mode 100644 index 00000000..4d22bf70 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/epositionadmin/set_min_health.cdc @@ -0,0 +1,27 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION — DO NOT USE IN PRODUCTION +/// +/// Verifies that storage ownership of PositionManager grants EPositionAdmin access to +/// PositionManager.borrowAuthorizedPosition and Position.setMinHealth. +/// EPositionAdmin comes exclusively from owning the PositionManager in storage +/// and cannot be delegated as a capability. +/// +/// @param pid: Own position ID to configure +/// @param minHealth: Minimum health factor before auto-borrow is triggered +transaction(pid: UInt64, minHealth: UFix64) { + let position: auth(FlowALPModels.EPositionAdmin) &FlowALPv0.Position + + prepare(signer: auth(BorrowValue) &Account) { + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("Could not borrow PositionManager with EPositionAdmin entitlement") + + self.position = manager.borrowAuthorizedPosition(pid: pid) + } + + execute { + self.position.setMinHealth(minHealth: minHealth) + } +} diff --git a/cadence/tests/transactions/flow-alp/epositionadmin/set_target_health.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/set_target_health.cdc new file mode 100644 index 00000000..f9198d8c --- /dev/null +++ b/cadence/tests/transactions/flow-alp/epositionadmin/set_target_health.cdc @@ -0,0 +1,27 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION — DO NOT USE IN PRODUCTION +/// +/// Verifies that storage ownership of PositionManager grants EPositionAdmin access to +/// PositionManager.borrowAuthorizedPosition and Position.setTargetHealth. +/// EPositionAdmin comes exclusively from owning the PositionManager in storage +/// and cannot be delegated as a capability. +/// +/// @param pid: Own position ID to configure +/// @param targetHealth: Target health factor to set +transaction(pid: UInt64, targetHealth: UFix64) { + let position: auth(FlowALPModels.EPositionAdmin) &FlowALPv0.Position + + prepare(signer: auth(BorrowValue) &Account) { + let manager = signer.storage.borrow( + from: FlowALPv0.PositionStoragePath + ) ?? panic("Could not borrow PositionManager with EPositionAdmin entitlement") + + self.position = manager.borrowAuthorizedPosition(pid: pid) + } + + execute { + self.position.setTargetHealth(targetHealth: targetHealth) + } +} diff --git a/cadence/tests/transactions/flow-alp/erebalance/pool_rebalance_position.cdc b/cadence/tests/transactions/flow-alp/erebalance/pool_rebalance_position.cdc new file mode 100644 index 00000000..dfa26a82 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/erebalance/pool_rebalance_position.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that auth(ERebalance) &Pool grants access to Pool.rebalancePosition. +/// ERebalance is the narrower entitlement specifically for rebalancing operations, +/// distinct from the broader EPosition entitlement. +/// +/// @param pid: The position ID to rebalance +/// @param force: Whether to force rebalance regardless of health bounds +transaction(pid: UInt64, force: Bool) { + let pool: auth(FlowALPModels.ERebalance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + self.pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) + ?? panic("Could not borrow Pool with ERebalance entitlement") + } + + execute { + self.pool.rebalancePosition(pid: pid, force: force) + } +} diff --git a/cadence/tests/transactions/flow-alp/erebalance/rebalance_pool.cdc b/cadence/tests/transactions/flow-alp/erebalance/rebalance_pool.cdc new file mode 100644 index 00000000..ffb48b71 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/erebalance/rebalance_pool.cdc @@ -0,0 +1,28 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST TRANSACTION - DO NOT USE IN PRODUCTION +/// +/// Verifies that Capability (rebalancer capability) grants: +/// Pool.rebalancePosition +/// +/// This simulates how FlowALPRebalancerv1 uses a narrow ERebalance capability +/// without being granted the broader EPosition entitlement. +/// +/// @param pid: Position to rebalance +/// @param force: Whether to force rebalance regardless of health bounds +transaction(pid: UInt64, force: Bool) { + let pool: auth(FlowALPModels.ERebalance) &FlowALPv0.Pool + + prepare(signer: auth(BorrowValue) &Account) { + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("ERebalance capability not found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool with ERebalance") + } + + execute { + // Pool.rebalancePosition — requires EPosition | ERebalance; ERebalance alone is sufficient + self.pool.rebalancePosition(pid: pid, force: force) + } +} diff --git a/cadence/tests/transactions/flow-alp/pool-management/manual_liquidation_chosen_vault.cdc b/cadence/tests/transactions/flow-alp/helpers/manual_liquidation_chosen_vault.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/pool-management/manual_liquidation_chosen_vault.cdc rename to cadence/tests/transactions/flow-alp/helpers/manual_liquidation_chosen_vault.cdc diff --git a/cadence/tests/transactions/flow-alp/pool-governance/remove_insurance_swapper.cdc b/cadence/tests/transactions/flow-alp/helpers/remove_insurance_swapper.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/pool-governance/remove_insurance_swapper.cdc rename to cadence/tests/transactions/flow-alp/helpers/remove_insurance_swapper.cdc diff --git a/cadence/tests/transactions/flow-alp/pool-governance/set_insurance_swapper_mock.cdc b/cadence/tests/transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/pool-governance/set_insurance_swapper_mock.cdc rename to cadence/tests/transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc diff --git a/cadence/tests/transactions/flow-alp/pool-governance/set_pool_paused.cdc b/cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc similarity index 69% rename from cadence/tests/transactions/flow-alp/pool-governance/set_pool_paused.cdc rename to cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc index 7777122b..0e955e92 100644 --- a/cadence/tests/transactions/flow-alp/pool-governance/set_pool_paused.cdc +++ b/cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc @@ -12,8 +12,10 @@ transaction(pause: Bool) { let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool prepare(signer: auth(BorrowValue) &Account) { - self.pool = signer.storage.borrow(from: FlowALPv0.PoolStoragePath) - ?? panic("Could not borrow Pool at \(FlowALPv0.PoolStoragePath)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") } execute { diff --git a/cadence/tests/transactions/flow-alp/pool-management/withdraw_from_position.cdc b/cadence/tests/transactions/flow-alp/helpers/withdraw_from_position.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/pool-management/withdraw_from_position.cdc rename to cadence/tests/transactions/flow-alp/helpers/withdraw_from_position.cdc diff --git a/cadence/tests/transactions/flow-alp/pool-management/01_negative_no_eparticipant_fail.cdc b/cadence/tests/transactions/flow-alp/pool-management/01_negative_no_eparticipant_fail.cdc deleted file mode 100644 index b0d499ea..00000000 --- a/cadence/tests/transactions/flow-alp/pool-management/01_negative_no_eparticipant_fail.cdc +++ /dev/null @@ -1,28 +0,0 @@ -import "FungibleToken" -import "DeFiActions" -import "DeFiActionsUtils" -import "FlowALPv0" -import "MOET" -import "DummyConnectors" - -/// Tries to call Pool.createPosition using a plain &Pool ref (no EParticipant). -/// This should fail at CHECKING with an access/entitlement error. -transaction { - prepare(admin: auth(BorrowValue, IssueStorageCapabilityController) &Account) { - // Issue a storage cap WITHOUT any entitlement - let cap = admin.capabilities.storage.issue<&FlowALPv0.Pool>( - FlowALPv0.PoolStoragePath - ) - let pool = cap.borrow() ?? panic("nil pool") - - - // EXPECTED: checker rejects this call (createPosition is access(EParticipant)). - let zero <- DeFiActionsUtils.getEmptyVault(Type<@MOET.Vault>()) - let _ = pool.createPosition( - funds: <- zero, - issuanceSink: DummyConnectors.DummySink(), - repaymentSource: nil, - pushToDrawDownSink: false - ) - } -} 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 deleted file mode 100644 index 0c148e10..00000000 --- a/cadence/tests/transactions/flow-alp/pool-management/04_create_position.cdc +++ /dev/null @@ -1,38 +0,0 @@ -import "FungibleToken" -import "DeFiActions" -import "DeFiActionsUtils" -import "FlowALPv0" -import "FlowALPModels" -import "MOET" -import "DummyConnectors" - -transaction { - prepare(admin: auth(BorrowValue, Storage, Capabilities) &Account) { - let pool = admin.storage.borrow(from: FlowALPv0.PoolStoragePath) - - // Ensure PositionManager exists - if admin.storage.borrow<&FlowALPv0.PositionManager>(from: FlowALPv0.PositionStoragePath) == nil { - let manager <- FlowALPv0.createPositionManager() - admin.storage.save(<-manager, to: FlowALPv0.PositionStoragePath) - } - - // Call EParticipant-gated methods - let zero1 <- DeFiActionsUtils.getEmptyVault(Type<@MOET.Vault>()) - let position <- pool.createPosition( - funds: <- zero1, - issuanceSink: DummyConnectors.DummySink(), - repaymentSource: nil, - pushToDrawDownSink: false - ) - - let pid = position.id - - // Add position to manager - let manager = admin.storage.borrow<&FlowALPv0.PositionManager>(from: FlowALPv0.PositionStoragePath)! - manager.addPosition(position: <-position) - - // Also allowed with EParticipant: - let zero2 <- DeFiActionsUtils.getEmptyVault(Type<@MOET.Vault>()) - pool.depositToPosition(pid: pid, from: <- zero2) - } -} diff --git a/cadence/tests/transactions/flow-alp/pool-management/05_negative_cap.cdc b/cadence/tests/transactions/flow-alp/pool-management/05_negative_cap.cdc deleted file mode 100644 index 206d0b3f..00000000 --- a/cadence/tests/transactions/flow-alp/pool-management/05_negative_cap.cdc +++ /dev/null @@ -1,20 +0,0 @@ -import "FlowALPv0" -import "FlowALPModels" - -// Intentionally executed by a NON-ADMIN account. -// Expected: PANIC when trying to borrow a governance-authorized ref. -transaction() { - - prepare(nonAdmin: auth(Capabilities) &Account) { - // Non-admin tries to issue a capability to the *admin’s* PoolFactory path. - // This account does NOT have the PoolFactory stored at that path, so the borrow() must fail. - let badGovCap: Capability = - nonAdmin.capabilities.storage.issue( - FlowALPv0.PoolFactoryPath - ) - - // This will return nil, triggering the panic — which is what we WANT in this negative test. - let _ = badGovCap.borrow() - ?? panic("Negative test passed: non-admin cannot borrow governance ref to PoolFactory") - } -} diff --git a/cadence/tests/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc b/cadence/tests/transactions/flow-alp/setup/claim_beta_cap.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/beta/claim_and_save_beta_cap.cdc rename to cadence/tests/transactions/flow-alp/setup/claim_beta_cap.cdc diff --git a/cadence/tests/transactions/flow-alp/pool-factory/create_and_store_pool.cdc b/cadence/tests/transactions/flow-alp/setup/create_and_store_pool.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/pool-factory/create_and_store_pool.cdc rename to cadence/tests/transactions/flow-alp/setup/create_and_store_pool.cdc diff --git a/cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc b/cadence/tests/transactions/flow-alp/setup/grant_beta_cap.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/pool-management/03_grant_beta.cdc rename to cadence/tests/transactions/flow-alp/setup/grant_beta_cap.cdc diff --git a/cadence/tests/transactions/flow-alp/setup/grant_egovernance_cap.cdc b/cadence/tests/transactions/flow-alp/setup/grant_egovernance_cap.cdc new file mode 100644 index 00000000..3feb2970 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/setup/grant_egovernance_cap.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST SETUP — grants an EGovernance Pool capability to a governance account. +/// Simulates capability-delegated governance access to the Pool. +/// Stored at FlowALPv0.PoolCapStoragePath. +transaction { + prepare( + admin: auth(IssueStorageCapabilityController) &Account, + user: auth(Storage) &Account + ) { + let cap = admin.capabilities.storage.issue( + FlowALPv0.PoolStoragePath + ) + // Overwrite any existing cap at this path + if user.storage.type(at: FlowALPv0.PoolCapStoragePath) != nil { + user.storage.load>( + from: FlowALPv0.PoolCapStoragePath + ) + } + user.storage.save(cap, to: FlowALPv0.PoolCapStoragePath) + } +} diff --git a/cadence/tests/transactions/flow-alp/setup/grant_eparticipant_cap.cdc b/cadence/tests/transactions/flow-alp/setup/grant_eparticipant_cap.cdc new file mode 100644 index 00000000..5164db1c --- /dev/null +++ b/cadence/tests/transactions/flow-alp/setup/grant_eparticipant_cap.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST SETUP — grants an EParticipant-ONLY Pool capability to a user account. +/// This is the FIXED beta capability — no EPosition over-grant. +/// Stored at FlowALPv0.PoolCapStoragePath. +transaction { + prepare( + admin: auth(IssueStorageCapabilityController) &Account, + user: auth(Storage) &Account + ) { + let cap = admin.capabilities.storage.issue( + FlowALPv0.PoolStoragePath + ) + // Overwrite any existing cap at this path + if user.storage.type(at: FlowALPv0.PoolCapStoragePath) != nil { + user.storage.load>( + from: FlowALPv0.PoolCapStoragePath + ) + } + user.storage.save(cap, to: FlowALPv0.PoolCapStoragePath) + } +} diff --git a/cadence/tests/transactions/flow-alp/setup/grant_eposition_cap.cdc b/cadence/tests/transactions/flow-alp/setup/grant_eposition_cap.cdc new file mode 100644 index 00000000..5fee69a5 --- /dev/null +++ b/cadence/tests/transactions/flow-alp/setup/grant_eposition_cap.cdc @@ -0,0 +1,25 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST SETUP — grants an EPosition-ONLY Pool capability to a user account. +/// This is a narrowly-scoped capability — no EParticipant, so the holder cannot +/// createPosition. EPosition alone allows pool-level position operations on any +/// position by ID (withdraw, depositAndPush, lockPosition, rebalancePosition, etc.). +/// Stored at FlowALPv0.PoolCapStoragePath. +transaction { + prepare( + admin: auth(IssueStorageCapabilityController) &Account, + user: auth(Storage) &Account + ) { + let cap = admin.capabilities.storage.issue( + FlowALPv0.PoolStoragePath + ) + // Overwrite any existing cap at this path + if user.storage.type(at: FlowALPv0.PoolCapStoragePath) != nil { + user.storage.load>( + from: FlowALPv0.PoolCapStoragePath + ) + } + user.storage.save(cap, to: FlowALPv0.PoolCapStoragePath) + } +} diff --git a/cadence/tests/transactions/flow-alp/setup/grant_erebalance_cap.cdc b/cadence/tests/transactions/flow-alp/setup/grant_erebalance_cap.cdc new file mode 100644 index 00000000..cf92101e --- /dev/null +++ b/cadence/tests/transactions/flow-alp/setup/grant_erebalance_cap.cdc @@ -0,0 +1,23 @@ +import "FlowALPv0" +import "FlowALPModels" + +/// TEST SETUP — grants an ERebalance Pool capability to a rebalancer account. +/// Simulates how FlowALPRebalancerv1 obtains narrowly-scoped rebalancing rights. +/// Stored at FlowALPv0.PoolCapStoragePath. +transaction { + prepare( + admin: auth(IssueStorageCapabilityController) &Account, + user: auth(Storage) &Account + ) { + let cap = admin.capabilities.storage.issue( + FlowALPv0.PoolStoragePath + ) + // Overwrite any existing cap at this path + if user.storage.type(at: FlowALPv0.PoolCapStoragePath) != nil { + user.storage.load>( + from: FlowALPv0.PoolCapStoragePath + ) + } + user.storage.save(cap, to: FlowALPv0.PoolCapStoragePath) + } +} diff --git a/docs/security-permission-matrix.md b/docs/security-permission-matrix.md index 379025cf..d19189fb 100644 --- a/docs/security-permission-matrix.md +++ b/docs/security-permission-matrix.md @@ -43,7 +43,8 @@ Maps each entitlement to the operations it permits. For audit/security review. | `setPauseParams` | Configure pause conditions | | | | | ✅ | | | `setDepositLimitFraction` | Cap deposits as fraction of pool | | | | | ✅ | | | `collectInsurance` | Sweep insurance fees to treasury | | | | | ✅ | | -| `withdrawStabilityFund` | Withdraw from stability reserve | | | | | ✅ | | +| `collectStability` | Sweep stability fees to treasury | | | | | ✅ | | +| `withdrawStabilityFund` | Withdraw from stability reserve³ | | | | | ✅ | | | `setDEX` / `setPriceOracle` | Set liquidation DEX or price feed | | | | | ✅ | | | `asyncUpdate` | Process queued state updates | | | | | | ✅ | | `asyncUpdatePosition` | Process queued update for one position | | | | | | ✅ | @@ -52,7 +53,8 @@ Maps each entitlement to the operations it permits. For audit/security review. | `delete` (RebalancerPaid) | Stop and remove paid rebalancer | `Delete`² | | | | | | ¹ `borrowAuthorizedPosition` requires `FungibleToken.Withdraw + EPositionAdmin` — both required (conjunction). -² Contract-local entitlements in `FlowALPRebalancerv1` / `FlowALPRebalancerPaidv1`, not part of the FlowALPv0 hierarchy. +² Contract-local entitlements in `FlowALPRebalancerv1` / `FlowALPRebalancerPaidv1`, not part of the FlowALPv0 hierarchy. Not tested in `cap_test.cdc`. Covered by `cadence/tests/paid_auto_balance_test.cdc`: `test_change_recurring_config` (positive, admin succeeds), `test_change_recurring_config_as_user` (negative, non-admin denied), `test_delete_rebalancer` (`Delete` entitlement). +³ `withdrawStabilityFund` requires an active stability fund (non-zero debit balance + elapsed time + non-zero fee rate). Covered by `cadence/tests/withdraw_stability_funds_test.cdc`. ## ⚠️ Known Issue: Beta Capability Over-Grant @@ -62,6 +64,14 @@ Maps each entitlement to the operations it permits. For audit/security review. **Fix:** Remove `EPosition` from the beta capability — grant `EParticipant` only. +## Test Coverage + +| Test file | What it covers | +|---|---| +| `cadence/tests/cap_test.cdc` | All `FlowALPv0.Pool` entitlements: `EParticipant`, `EParticipant+EPosition` (over-grant), `EPosition`, `ERebalance`, `EPositionAdmin`, `EGovernance`, `EImplementation` — one test per matrix row | +| `cadence/tests/paid_auto_balance_test.cdc` | Rebalancer-contract entitlements: `Configure` (`setRecurringConfig`), `Delete` (`delete`) | +| `cadence/tests/withdraw_stability_funds_test.cdc` | `EGovernance` → `withdrawStabilityFund` (requires live stability fund state) | + ## Audit Notes - `rebalancePosition` / `rebalance` use `EPosition | ERebalance` — **either** entitlement is sufficient (union, not conjunction) From 491193aed62f669f20d6e3a7650ad45122114ca1 Mon Sep 17 00:00:00 2001 From: Taras Maliarchuk Date: Fri, 6 Mar 2026 16:10:41 +0100 Subject: [PATCH 06/10] fix comments --- cadence/tests/cap_test.cdc | 28 +++++++++++----------------- 1 file changed, 11 insertions(+), 17 deletions(-) diff --git a/cadence/tests/cap_test.cdc b/cadence/tests/cap_test.cdc index 4a1c7fe6..3d4d019c 100644 --- a/cadence/tests/cap_test.cdc +++ b/cadence/tests/cap_test.cdc @@ -44,23 +44,13 @@ import "test_helpers.cdc" // PROTOCOL_ACCOUNT — Pool owner; exercises EImplementation directly via storage borrow. // // Negative tests: -// Cadence entitlements for Pool capabilities (EParticipant, EPosition, ERebalance, -// EGovernance, EImplementation) are enforced by the type checker at check time. -// A transaction that calls an entitlement-gated method without holding that entitlement -// is rejected before it can be submitted — no runtime negative test is needed. -// -// Runtime negative tests are present where access is enforced at runtime, not statically: -// 1. Cap not issued — a user without a cap at a given storage path gets nil from borrow -// and panics. Representative case: testEPositionAdmin_SetTargetHealth_Neg -// (ePositionUser has no PositionManager). -// 2. Ownership check — a user with a PositionManager requests a pid not in their manager. -// Representative case: testEPositionAdmin_BorrowUnauthorized_Fails. -// +// Cadence entitlements for Pool capabilities are enforced by the Cadence type checker. +// Only borrowAuthorizedPosition has a runtime enforcement (it panics if the pid is not in +// the signer's PositionManager), so testEPositionAdmin_BorrowUnauthorizedPosition_Fails tests that path. // ============================================================================= // Position created for PROTOCOL_ACCOUNT in setup — used as target for EPosition tests. -// Pool.nextPositionID starts at 0; the first createPosition call produces pid=0. access(all) var setupPid: UInt64 = 0 access(all) var ePositionAdminPid: UInt64 = 0 @@ -273,7 +263,8 @@ fun testEParticipant_CreateAndDeposit() { // // Matrix rows: createPosition (EParticipant), depositToPosition (EParticipant), // withdraw [OVERGRANT], withdrawAndPull [OVERGRANT], depositAndPush [OVERGRANT], -// lockPosition [OVERGRANT], unlockPosition [OVERGRANT], rebalancePosition [OVERGRANT] +// lockPosition [OVERGRANT], unlockPosition [OVERGRANT], rebalancePosition [OVERGRANT], +// rebalance (Position) [OVERGRANT — same entry point as rebalancePosition] // // The [OVERGRANT] rows confirm the security issue: a normal beta user can operate on // positions they do not own (setupPid is owned by PROTOCOL_ACCOUNT). @@ -460,9 +451,12 @@ fun testERebalance_RebalancePosition() { Test.expect(result, Test.beSucceeded()) } -/// ERebalance cap exercises Pool.rebalancePosition, which is the target of Position.rebalance(). -/// The contract fix (EPosition | ERebalance on Position.pool) ensures the internal call chain -/// for Position.rebalance() works under ERebalance. Both matrix rows share this entry point. +/// Matrix row: rebalance (Position) — Position.rebalance() delegates to Pool.rebalancePosition() +/// internally, so both matrix rows share the same Pool-level entry point. There is no separate +/// transaction that calls Position.rebalance() directly; this test confirms the ERebalance +/// entitlement is sufficient for the rebalancePosition call that Position.rebalance() invokes. +/// (The contract fix changes Position.pool to Capability +/// so the internal call chain accepts ERebalance callers.) access(all) fun testERebalance_PositionRebalance() { safeReset() From bd0dc05f226929d59a9d7f9d42028bb191b8a712 Mon Sep 17 00:00:00 2001 From: Taras Maliarchuk Date: Tue, 10 Mar 2026 22:10:45 +0100 Subject: [PATCH 07/10] fix wrong path for capability EGovernance --- .../transactions/flow-alp/helpers/set_pool_paused.cdc | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc b/cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc index 0e955e92..bb96f316 100644 --- a/cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc +++ b/cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc @@ -12,10 +12,9 @@ transaction(pause: Bool) { let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool prepare(signer: auth(BorrowValue) &Account) { - let cap = signer.storage.borrow<&Capability>( - from: FlowALPv0.PoolCapStoragePath - ) ?? panic("No EGovernance cap found") - self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") + self.pool = signer.storage.borrow( + from: FlowALPv0.PoolStoragePath + ) ?? panic("Could not borrow Pool at \(FlowALPv0.PoolStoragePath)") } execute { From dee9360c9e1ea94c7e30712a400044e08980631a Mon Sep 17 00:00:00 2001 From: Taras Maliarchuk Date: Tue, 10 Mar 2026 22:33:05 +0100 Subject: [PATCH 08/10] move some scripts from the `helper` folder to appropriate paths --- .../tests/adversarial_recursive_withdraw_source_test.cdc | 2 +- cadence/tests/adversarial_type_spoofing_test.cdc | 2 +- cadence/tests/cap_test.cdc | 4 ++-- cadence/tests/insurance_swapper_test.cdc | 4 ++-- cadence/tests/test_helpers.cdc | 6 +++--- .../{helpers => egovernance}/remove_insurance_swapper.cdc | 0 .../{helpers => egovernance}/set_insurance_swapper_mock.cdc | 0 .../flow-alp/{helpers => egovernance}/set_pool_paused.cdc | 0 .../{helpers => epositionadmin}/withdraw_from_position.cdc | 0 9 files changed, 9 insertions(+), 9 deletions(-) rename cadence/tests/transactions/flow-alp/{helpers => egovernance}/remove_insurance_swapper.cdc (100%) rename cadence/tests/transactions/flow-alp/{helpers => egovernance}/set_insurance_swapper_mock.cdc (100%) rename cadence/tests/transactions/flow-alp/{helpers => egovernance}/set_pool_paused.cdc (100%) rename cadence/tests/transactions/flow-alp/{helpers => epositionadmin}/withdraw_from_position.cdc (100%) diff --git a/cadence/tests/adversarial_recursive_withdraw_source_test.cdc b/cadence/tests/adversarial_recursive_withdraw_source_test.cdc index 4a6ddd6e..f3b8c7ed 100644 --- a/cadence/tests/adversarial_recursive_withdraw_source_test.cdc +++ b/cadence/tests/adversarial_recursive_withdraw_source_test.cdc @@ -120,7 +120,7 @@ fun testRecursiveWithdrawSource() { // In this test, the topUpSource behavior is adversarial: it attempts to re-enter // the pool during the pull/deposit flow. We expect the transaction to fail. let withdrawRes = executeTransaction( - "./transactions/flow-alp/helpers/withdraw_from_position.cdc", + "./transactions/flow-alp/epositionadmin/withdraw_from_position.cdc", [positionID, flowTokenIdentifier, 1500.0, true], // pullFromTopUpSource: true userAccount ) diff --git a/cadence/tests/adversarial_type_spoofing_test.cdc b/cadence/tests/adversarial_type_spoofing_test.cdc index 4e12c4db..2d793af8 100644 --- a/cadence/tests/adversarial_type_spoofing_test.cdc +++ b/cadence/tests/adversarial_type_spoofing_test.cdc @@ -59,7 +59,7 @@ fun testMaliciousSource() { // withdraw 1337 Flow from the position let withdrawRes = executeTransaction( - "./transactions/flow-alp/helpers/withdraw_from_position.cdc", + "./transactions/flow-alp/epositionadmin/withdraw_from_position.cdc", [1 as UInt64, flowTokenIdentifier, 1337.0, true], hackerAccount ) diff --git a/cadence/tests/cap_test.cdc b/cadence/tests/cap_test.cdc index 3d4d019c..af16d61d 100644 --- a/cadence/tests/cap_test.cdc +++ b/cadence/tests/cap_test.cdc @@ -613,14 +613,14 @@ fun testEGovernance_PauseUnpause() { safeReset() let pauseResult = _executeTransaction( - "../tests/transactions/flow-alp/helpers/set_pool_paused.cdc", + "../tests/transactions/flow-alp/egovernance/set_pool_paused.cdc", [true], eGovernanceUser ) Test.expect(pauseResult, Test.beSucceeded()) let unpauseResult = _executeTransaction( - "../tests/transactions/flow-alp/helpers/set_pool_paused.cdc", + "../tests/transactions/flow-alp/egovernance/set_pool_paused.cdc", [false], eGovernanceUser ) diff --git a/cadence/tests/insurance_swapper_test.cdc b/cadence/tests/insurance_swapper_test.cdc index 9bb0ab2a..fdd70637 100644 --- a/cadence/tests/insurance_swapper_test.cdc +++ b/cadence/tests/insurance_swapper_test.cdc @@ -182,7 +182,7 @@ access(all) fun test_setInsuranceSwapper_wrongOutputType_fails() { // try to set a swapper that doesn't output MOET (outputs FLOW_TOKEN_IDENTIFIER instead) let res = _executeTransaction( - "./transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc", + "./transactions/flow-alp/egovernance/set_insurance_swapper_mock.cdc", [MOET_TOKEN_IDENTIFIER, 1.0, MOET_TOKEN_IDENTIFIER, FLOW_TOKEN_IDENTIFIER], PROTOCOL_ACCOUNT ) @@ -199,7 +199,7 @@ access(all) fun test_setInsuranceSwapper_wrongInputType_fails() { // try to set a swapper with wrong input type (FLOW_TOKEN_IDENTIFIER instead of MOET_TOKEN_IDENTIFIER) let res = _executeTransaction( - "./transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc", + "./transactions/flow-alp/egovernance/set_insurance_swapper_mock.cdc", [MOET_TOKEN_IDENTIFIER, 1.0, FLOW_TOKEN_IDENTIFIER, MOET_TOKEN_IDENTIFIER], PROTOCOL_ACCOUNT ) diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 8e02ea11..3810729b 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -532,7 +532,7 @@ fun setPoolPauseState( pause: Bool ): Test.TransactionResult { return _executeTransaction( - "./transactions/flow-alp/helpers/set_pool_paused.cdc", + "./transactions/flow-alp/egovernance/set_pool_paused.cdc", [pause], signer ) @@ -677,7 +677,7 @@ fun setInsuranceSwapper( priceRatio: UFix64, ): Test.TransactionResult { let res = _executeTransaction( - "./transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc", + "./transactions/flow-alp/egovernance/set_insurance_swapper_mock.cdc", [ tokenTypeIdentifier, priceRatio, tokenTypeIdentifier, MOET_TOKEN_IDENTIFIER], signer ) @@ -690,7 +690,7 @@ fun removeInsuranceSwapper( tokenTypeIdentifier: String, ): Test.TransactionResult { let res = _executeTransaction( - "./transactions/flow-alp/helpers/remove_insurance_swapper.cdc", + "./transactions/flow-alp/egovernance/remove_insurance_swapper.cdc", [ tokenTypeIdentifier], signer ) diff --git a/cadence/tests/transactions/flow-alp/helpers/remove_insurance_swapper.cdc b/cadence/tests/transactions/flow-alp/egovernance/remove_insurance_swapper.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/helpers/remove_insurance_swapper.cdc rename to cadence/tests/transactions/flow-alp/egovernance/remove_insurance_swapper.cdc diff --git a/cadence/tests/transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_insurance_swapper_mock.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/helpers/set_insurance_swapper_mock.cdc rename to cadence/tests/transactions/flow-alp/egovernance/set_insurance_swapper_mock.cdc diff --git a/cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_pool_paused.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/helpers/set_pool_paused.cdc rename to cadence/tests/transactions/flow-alp/egovernance/set_pool_paused.cdc diff --git a/cadence/tests/transactions/flow-alp/helpers/withdraw_from_position.cdc b/cadence/tests/transactions/flow-alp/epositionadmin/withdraw_from_position.cdc similarity index 100% rename from cadence/tests/transactions/flow-alp/helpers/withdraw_from_position.cdc rename to cadence/tests/transactions/flow-alp/epositionadmin/withdraw_from_position.cdc From 27dc3b5048080bf9d1a2bee7ed9d16274a5a3476 Mon Sep 17 00:00:00 2001 From: Taras Maliarchuk Date: Tue, 10 Mar 2026 22:41:27 +0100 Subject: [PATCH 09/10] typo fix --- cadence/tests/cap_test.cdc | 4 ++-- cadence/tests/test_helpers.cdc | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/cadence/tests/cap_test.cdc b/cadence/tests/cap_test.cdc index af16d61d..d6d59f2e 100644 --- a/cadence/tests/cap_test.cdc +++ b/cadence/tests/cap_test.cdc @@ -117,7 +117,7 @@ fun setup() { pushToDrawDownSink: false ) - setupPid = getLastPositonId() + setupPid = getLastPositionId() // ───────────────────────────────────────────────────────────────────────── // EParticipant user — EParticipant-ONLY capability (fixed beta cap) @@ -182,7 +182,7 @@ fun setup() { vaultStoragePath: MOET.VaultStoragePath, pushToDrawDownSink: false ) - ePositionAdminPid = getLastPositonId() + ePositionAdminPid = getLastPositionId() // ───────────────────────────────────────────────────────────────────────── // EGovernance user — EGovernance capability delegated from PROTOCOL_ACCOUNT diff --git a/cadence/tests/test_helpers.cdc b/cadence/tests/test_helpers.cdc index 1c0aa2a3..7adb2fb9 100644 --- a/cadence/tests/test_helpers.cdc +++ b/cadence/tests/test_helpers.cdc @@ -1015,7 +1015,7 @@ fun getCreditBalanceForType(details: FlowALPModels.PositionDetails, vaultType: T } access(all) -fun getLastPositonId(): UInt64 { +fun getLastPositionId(): UInt64 { var openEvents = Test.eventsOfType(Type()) if openEvents.length > 0 { let pid = (openEvents[openEvents.length - 1] as! FlowALPEvents.Opened).pid From ca275cfab1326309efc7f658711f723a60f25e07 Mon Sep 17 00:00:00 2001 From: Taras Maliarchuk Date: Tue, 10 Mar 2026 23:36:12 +0100 Subject: [PATCH 10/10] fix pool_pause_test.cdc to change from direct pool storage borrow to capability-based borrow --- cadence/tests/pool_pause_test.cdc | 8 ++++++++ .../transactions/flow-alp/egovernance/set_pool_paused.cdc | 7 ++++--- 2 files changed, 12 insertions(+), 3 deletions(-) diff --git a/cadence/tests/pool_pause_test.cdc b/cadence/tests/pool_pause_test.cdc index 40e0f6a6..d1989bda 100644 --- a/cadence/tests/pool_pause_test.cdc +++ b/cadence/tests/pool_pause_test.cdc @@ -30,6 +30,14 @@ fun setup() { depositRate: 1_000_000.0, depositCapacityCap: 1_000_000.0 ) + // Grant PROTOCOL_ACCOUNT an EGovernance cap so setPoolPauseState (cap-based) works. + let grantResult = Test.executeTransaction(Test.Transaction( + code: Test.readFile("./transactions/flow-alp/setup/grant_egovernance_cap.cdc"), + authorizers: [PROTOCOL_ACCOUNT.address, PROTOCOL_ACCOUNT.address], + signers: [PROTOCOL_ACCOUNT], + arguments: [] + )) + Test.expect(grantResult, Test.beSucceeded()) snapshot = getCurrentBlockHeight() } diff --git a/cadence/tests/transactions/flow-alp/egovernance/set_pool_paused.cdc b/cadence/tests/transactions/flow-alp/egovernance/set_pool_paused.cdc index bb96f316..0e955e92 100644 --- a/cadence/tests/transactions/flow-alp/egovernance/set_pool_paused.cdc +++ b/cadence/tests/transactions/flow-alp/egovernance/set_pool_paused.cdc @@ -12,9 +12,10 @@ transaction(pause: Bool) { let pool: auth(FlowALPModels.EGovernance) &FlowALPv0.Pool prepare(signer: auth(BorrowValue) &Account) { - self.pool = signer.storage.borrow( - from: FlowALPv0.PoolStoragePath - ) ?? panic("Could not borrow Pool at \(FlowALPv0.PoolStoragePath)") + let cap = signer.storage.borrow<&Capability>( + from: FlowALPv0.PoolCapStoragePath + ) ?? panic("No EGovernance cap found") + self.pool = cap.borrow() ?? panic("Could not borrow Pool from EGovernance cap") } execute {