diff --git a/examples/aave/README.md b/examples/aave/README.md index 4e96ef6..195cbb5 100644 --- a/examples/aave/README.md +++ b/examples/aave/README.md @@ -2,6 +2,12 @@ Assertion examples and supporting helpers extracted from the `aave` branch. +## Availability + +The v4 Hub and Spoke wrappers are quarantined and register no triggers. Their fixed scan bounds +can become governance liveness limits even though upstream reserve/spoke creation has no matching +cap. V3/v4 tolerance configuration rejects 10,000 bps because that value disables ratio checks. + ## Build ```sh diff --git a/examples/aave/src/AaveV3HorizonOracleAssertion.sol b/examples/aave/src/AaveV3HorizonOracleAssertion.sol index 0c0284f..8fcfe53 100644 --- a/examples/aave/src/AaveV3HorizonOracleAssertion.sol +++ b/examples/aave/src/AaveV3HorizonOracleAssertion.sol @@ -26,7 +26,7 @@ contract AaveV3HorizonOracleAssertion is AaveV3HorizonHelpers { require(pool_ != address(0), "AaveV3Horizon: pool zero"); require(addressesProvider_ != address(0), "AaveV3Horizon: provider zero"); require(maxReservesToScan_ != 0, "AaveV3Horizon: max reserves zero"); - require(oracleDeviationBps_ <= BPS, "AaveV3Horizon: bad oracle tolerance"); + require(oracleDeviationBps_ < BPS, "AaveV3Horizon: bad oracle tolerance"); POOL = pool_; ADDRESSES_PROVIDER = addressesProvider_; diff --git a/examples/aave/src/AaveV4HubAccountingAssertion.sol b/examples/aave/src/AaveV4HubAccountingAssertion.sol index 90f2b4a..10f9fe7 100644 --- a/examples/aave/src/AaveV4HubAccountingAssertion.sol +++ b/examples/aave/src/AaveV4HubAccountingAssertion.sol @@ -22,7 +22,7 @@ contract AaveV4HubAccountingAssertion is AaveV4Helpers { constructor(address hub_, uint256 assetId_, uint256 maxSpokesToScan_, uint256 sharePriceToleranceBps_) { require(hub_ != address(0), "AaveV4Hub: hub zero"); require(maxSpokesToScan_ > 0, "AaveV4Hub: max spokes zero"); - require(sharePriceToleranceBps_ <= BPS, "AaveV4Hub: bad tolerance"); + require(sharePriceToleranceBps_ < BPS, "AaveV4Hub: bad tolerance"); HUB = hub_; ASSET_ID = assetId_; @@ -34,22 +34,8 @@ contract AaveV4HubAccountingAssertion is AaveV4Helpers { /// @dev The assertion is configured for one `assetId`; calls for other assets no-op after /// decoding the first calldata argument. function triggers() external view override { - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.add.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.remove.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.draw.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.restore.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.reportDeficit.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.refreshPremium.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.payFeeShares.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.transferShares.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.mintFeeShares.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.eliminateDeficit.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.sweep.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.reclaim.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.updateAssetConfig.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.addSpoke.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.updateSpokeConfig.selector); - registerFnCallTrigger(this.assertHubAssetAccounting.selector, IAaveV4Hub.setInterestRateData.selector); + // Quarantined: immutable scan bounds become governance liveness caps because addSpoke has + // no matching protocol limit. Re-enable only with a bounded incremental accounting model. } /// @notice Checks one Hub asset remains backed and internally coherent after a Hub mutation. diff --git a/examples/aave/src/AaveV4SpokeRiskAssertion.sol b/examples/aave/src/AaveV4SpokeRiskAssertion.sol index 151a58b..f30d594 100644 --- a/examples/aave/src/AaveV4SpokeRiskAssertion.sol +++ b/examples/aave/src/AaveV4SpokeRiskAssertion.sol @@ -37,7 +37,7 @@ contract AaveV4SpokeRiskAssertion is AaveV4Helpers { constructor(address spoke_, uint256 maxReservesToScan_, uint256 oracleDeviationBps_) { require(spoke_ != address(0), "AaveV4Spoke: spoke zero"); require(maxReservesToScan_ > 0, "AaveV4Spoke: max reserves zero"); - require(oracleDeviationBps_ <= BPS, "AaveV4Spoke: bad oracle tolerance"); + require(oracleDeviationBps_ < BPS, "AaveV4Spoke: bad oracle tolerance"); SPOKE = spoke_; MAX_RESERVES_TO_SCAN = maxReservesToScan_; @@ -48,24 +48,8 @@ contract AaveV4SpokeRiskAssertion is AaveV4Helpers { /// @dev Calls that intentionally refresh stored risk premium are distinguished from paths /// that only change collateral composition without refreshing premium debt. function triggers() external view override { - registerFnCallTrigger(this.assertAccountDataMatchesIndependentState.selector, IAaveV4Spoke.withdraw.selector); - registerFnCallTrigger(this.assertAccountDataMatchesIndependentState.selector, IAaveV4Spoke.borrow.selector); - registerFnCallTrigger( - this.assertAccountDataMatchesIndependentState.selector, IAaveV4Spoke.setUsingAsCollateral.selector - ); - registerFnCallTrigger( - this.assertAccountDataMatchesIndependentState.selector, IAaveV4Spoke.updateUserRiskPremium.selector - ); - registerFnCallTrigger( - this.assertAccountDataMatchesIndependentState.selector, IAaveV4Spoke.updateUserDynamicConfig.selector - ); - registerFnCallTrigger( - this.assertAccountDataMatchesIndependentState.selector, IAaveV4Spoke.liquidationCall.selector - ); - - registerFnCallTrigger( - this.assertLiquidationReducesBorrowerDebt.selector, IAaveV4Spoke.liquidationCall.selector - ); + // Quarantined: reserve enumeration has no protocol-level maximum and therefore cannot be + // represented safely by this immutable deployment bound. } /// @notice Recomputes account data from primitive state and compares it to the Spoke view. diff --git a/examples/aave/test/AaveV4HubAccountingAssertion.t.sol b/examples/aave/test/AaveV4HubAccountingAssertion.t.sol index 4d8760b..a8ebf73 100644 --- a/examples/aave/test/AaveV4HubAccountingAssertion.t.sol +++ b/examples/aave/test/AaveV4HubAccountingAssertion.t.sol @@ -98,12 +98,22 @@ contract AaveV4HubAccountingAssertionTest is Test, CredibleTest { cl.assertion(address(hub), createData, AaveV4HubAccountingAssertion.assertHubAssetAccounting.selector); } - function testHubAccountingPassesWhenSpokeSumsMatch() public { + function testPublishedWrapperCannotRegisterUnsafeChecks() public { + AaveV4HubAccountingAssertion assertion = new AaveV4HubAccountingAssertion(address(hub), 1, 4, 0); + vm.mockCallRevert( + address(uint160(uint256(keccak256("TriggerRecorder")))), + bytes(""), + bytes("quarantined wrapper registered a trigger") + ); + assertion.triggers(); + } + + function retiredHubAccountingPassesWhenSpokeSumsMatch() public { _arm(); hub.add(1, 0); } - function testHubAccountingTripsOnAggregateSpokeMismatch() public { + function retiredHubAccountingTripsOnAggregateSpokeMismatch() public { hub.setBreakSpokeSum(true); _arm(); diff --git a/examples/balancer/README.md b/examples/balancer/README.md index 87e4dad..b44f0d6 100644 --- a/examples/balancer/README.md +++ b/examples/balancer/README.md @@ -2,6 +2,11 @@ Assertion examples for Balancer V3's singleton Vault architecture. +> **Unavailable:** `BalancerV3VaultAssertion` is quarantined and registers no triggers. Hook and +> token-rate behavior must be derived from live registered Vault configuration and bound to a +> supported pool/factory revision before this wrapper can be activated. The outflow circuit breaker +> remains a separate operational policy. + ## Build ```sh diff --git a/examples/balancer/src/BalancerV3VaultAssertion.sol b/examples/balancer/src/BalancerV3VaultAssertion.sol index 3d84729..d180089 100644 --- a/examples/balancer/src/BalancerV3VaultAssertion.sol +++ b/examples/balancer/src/BalancerV3VaultAssertion.sol @@ -60,21 +60,8 @@ contract BalancerV3VaultAssertion is BalancerV3VaultHelpers { /// on the watched pool's own accounting deltas and the custody check compares per-token /// deficits across the transaction — both bounded by the pool's token count. function triggers() external view override { - registerFnCallTrigger(this.assertSwapPreservesPoolInvariant.selector, IBalancerV3VaultLike.swap.selector); - registerFnCallTrigger( - this.assertOperationRatesWithinBaseline.selector, IBalancerV3VaultLike.addLiquidity.selector - ); - registerFnCallTrigger( - this.assertOperationRatesWithinBaseline.selector, IBalancerV3VaultLike.removeLiquidity.selector - ); - registerFnCallTrigger( - this.assertOperationRatesWithinBaseline.selector, IBalancerV3VaultLike.initialize.selector - ); - registerFnCallTrigger( - this.assertOperationRatesWithinBaseline.selector, IBalancerV3VaultLike.disableRecoveryMode.selector - ); - registerTxEndTrigger(this.assertPoolAccountingWithinVaultCustody.selector); - registerTxEndTrigger(this.assertTokenRatesWithinDriftBound.selector); + // Quarantined: hook classification must be read from the Vault and the supported rate + // policy must be derived from the registered token configuration, not deployment flags. } /// @notice A swap on a hookless pool must grow (or at worst preserve) the pool invariant, diff --git a/examples/balancer/test/BalancerV3VaultAssertion.t.sol b/examples/balancer/test/BalancerV3VaultAssertion.t.sol index efb74ec..ddb0f18 100644 --- a/examples/balancer/test/BalancerV3VaultAssertion.t.sol +++ b/examples/balancer/test/BalancerV3VaultAssertion.t.sol @@ -56,6 +56,18 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { cl.assertion(address(vault), createData, fnSelector); } + function testPublishedWrapperCannotRegisterUnsafeChecks() public { + BalancerV3VaultAssertion assertion = new BalancerV3VaultAssertion( + address(vault), address(pool), vault.swapHooks(), INVARIANT_DUST_TOLERANCE, RATE_DRIFT_TOLERANCE_BPS + ); + vm.mockCallRevert( + address(uint160(uint256(keccak256("TriggerRecorder")))), + bytes(""), + bytes("quarantined wrapper registered a trigger") + ); + assertion.triggers(); + } + function _swapParams(address targetPool) internal view returns (VaultSwapParams memory) { return VaultSwapParams({ kind: SwapKind.EXACT_IN, @@ -92,7 +104,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- assertSwapPreservesPoolInvariant ------------------------------------ - function testHonestSwapPassesInvariantAssertion() public { + function retiredHonestSwapPassesInvariantAssertion() public { _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); _swap(address(pool)); } @@ -100,21 +112,21 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice The Vault deducts pending yield fees before adding swap input. Raw tokenIn balance /// can therefore fall across an honest small swap; fee-adjusted live balance still /// moves in the correct direction and is the relevant input to pool math. - function testHonestSwapWithPendingYieldFeesPassesInvariantAssertion() public { + function retiredHonestSwapWithPendingYieldFeesPassesInvariantAssertion() public { vault.seedPendingYieldFee(address(token0), 20e18); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); _swap(address(pool)); } - function testSwapOnUnwatchedPoolIsIgnored() public { + function retiredSwapOnUnwatchedPoolIsIgnored() public { vault.setMode(MockBalancerV3Vault.Mode.InvariantLoss); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); _swap(makeAddr("otherPool")); } - function testInvariantLossTrips() public { + function retiredInvariantLossTrips() public { vault.setMode(MockBalancerV3Vault.Mode.InvariantLoss); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); @@ -122,7 +134,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { _swap(address(pool)); } - function testSupplyDriftTrips() public { + function retiredSupplyDriftTrips() public { vault.setMode(MockBalancerV3Vault.Mode.SupplyDrift); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); @@ -130,7 +142,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { _swap(address(pool)); } - function testBalancesAgainstSwapDirectionTrip() public { + function retiredBalancesAgainstSwapDirectionTrip() public { vault.setMode(MockBalancerV3Vault.Mode.BalanceSwapEnds); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); @@ -142,7 +154,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// the swap check's scope: those hooks may legitimately reenter the Vault mid-swap, so /// call-boundary snapshots cannot attribute deltas to the core swap. A failure knob /// that would otherwise trip must pass once the deployment marks the pool as hooked. - function testHookedPoolSwapChecksAreSkipped() public { + function retiredHookedPoolSwapChecksAreSkipped() public { vault.setSwapHooks(true); vault.setMode(MockBalancerV3Vault.Mode.InvariantLoss); @@ -154,7 +166,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// restored after it, inside one transaction. Both transaction endpoints agree, but /// the swap priced against the shifted rate — the per-operation baseline observation /// catches what endpoint comparison cannot. - function testTransientRateManipulationAroundSwapTrips() public { + function retiredTransientRateManipulationAroundSwapTrips() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); token0.mint(address(router), 100e18); router.approveVault(address(token0)); @@ -167,7 +179,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice Documents the endpoint-comparison gap the per-operation check exists for: the same /// manipulate-swap-restore transaction passes the tx-end drift assertion because the /// pre-tx and post-tx rates are equal. - function testTransientRateManipulationPassesEndpointDrift() public { + function retiredTransientRateManipulationPassesEndpointDrift() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); token0.mint(address(router), 100e18); router.approveVault(address(token0)); @@ -176,7 +188,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { router.manipulateSwapRestore(_swapParams(address(pool))); } - function testTransientRateManipulationAroundAddLiquidityTrips() public { + function retiredTransientRateManipulationAroundAddLiquidityTrips() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); @@ -184,12 +196,12 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { router.manipulateAddLiquidityRestore(_addLiquidityParams(address(pool))); } - function testHonestAddLiquidityPassesScopedRateAssertion() public { + function retiredHonestAddLiquidityPassesScopedRateAssertion() public { _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); vault.addLiquidity(_addLiquidityParams(address(pool))); } - function testTransientRateManipulationAroundRemoveLiquidityTrips() public { + function retiredTransientRateManipulationAroundRemoveLiquidityTrips() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); @@ -199,12 +211,12 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- assertPoolAccountingWithinVaultCustody ------------------------------- - function testHonestSwapPassesCustodyAssertion() public { + function retiredHonestSwapPassesCustodyAssertion() public { _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); _swap(address(pool)); } - function testReserveSkimTripsCustody() public { + function retiredReserveSkimTripsCustody() public { vault.setMode(MockBalancerV3Vault.Mode.ReserveSkim); _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -212,7 +224,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { _swap(address(pool)); } - function testPhantomPoolBalanceTripsCustody() public { + function retiredPhantomPoolBalanceTripsCustody() public { vault.setMode(MockBalancerV3Vault.Mode.PhantomBalance); _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -224,7 +236,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// stay untouched: no watched-pool accounting delta exists, yet the deficit comparison /// still blocks the causing transaction instead of deferring detection to the pool's /// next accounting-moving transaction. - function testCustodyDrainWithoutAccountingChangeTrips() public { + function retiredCustodyDrainWithoutAccountingChangeTrips() public { _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); vm.expectRevert(bytes("BalancerV3: vault reserves exceed real token custody")); vault.sweepCustodyOnly(address(token0), 10e18); @@ -232,7 +244,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice A deficit that predates the transaction is not a license: a transaction that /// deepens it still trips, even though the absolute bound was already violated before. - function testWorseningPreexistingDeficitTrips() public { + function retiredWorseningPreexistingDeficitTrips() public { vault.seedPoolBalance(1, 1_600e18); // claim already exceeds reserves before arming vault.setMode(MockBalancerV3Vault.Mode.PhantomBalance); @@ -243,7 +255,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice A pool already in deficit must remain usable by honest traffic: an operation that /// reduces the pool's claims (still violated in absolute terms, but no worse) passes. - function testHonestOperationOnDeficientPoolPasses() public { + function retiredHonestOperationOnDeficientPoolPasses() public { vault.seedPoolBalance(0, 2_000e18); // claim exceeds reserves before arming _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -252,12 +264,12 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- assertTokenRatesWithinDriftBound -------------------------------------- - function testHonestSwapPassesRateAssertion() public { + function retiredHonestSwapPassesRateAssertion() public { _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); _swap(address(pool)); } - function testRateShiftTrips() public { + function retiredRateShiftTrips() public { vault.setMode(MockBalancerV3Vault.Mode.RateShift); _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); @@ -268,7 +280,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice A provider already registered for the pool pre-tx that answered ZERO pre-tx is a /// broken baseline, not a deployment lifecycle: it must fail instead of granting the /// registration exemption and legitimizing an arbitrary post-tx rate. - function testZeroBaselineForRegisteredProviderTrips() public { + function retiredZeroBaselineForRegisteredProviderTrips() public { rateProvider.setRate(0); vault.setMode(MockBalancerV3Vault.Mode.RateShift); // 0 -> 1e18 during the swap @@ -280,14 +292,14 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice A provider deployed and registered within the transaction has no pre-tx baseline by /// construction: the deployment lifecycle is exempt from the drift comparison (only the /// nonzero post-state is enforced) instead of reverting on the missing baseline read. - function testProviderRegisteredDuringTxIsExempt() public { + function retiredProviderRegisteredDuringTxIsExempt() public { vault.registerNewRateProvider(); _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); vault.initialize(address(pool), address(this), new address[](0), new uint256[](0), 0, ""); } - function testProviderRegisteredDuringTxPassesScopedInitializationRateCheck() public { + function retiredProviderRegisteredDuringTxPassesScopedInitializationRateCheck() public { vault.registerNewRateProvider(); _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); @@ -297,7 +309,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice Recovery mode disables the rate assertion entirely: Balancer's recovery exit uses /// raw balances precisely because providers may be broken, and a broken or moved /// provider must never block that path. - function testRecoveryModeSkipsRateAssertion() public { + function retiredRecoveryModeSkipsRateAssertion() public { vault.setRecoveryMode(true); vault.setMode(MockBalancerV3Vault.Mode.RateShift); @@ -309,7 +321,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// out of the drift assertion's scope, so the watched provider never becomes a /// dependency of unrelated Vault traffic. The residual is documented on the assertion: /// a later transaction consuming the moved rate touches the pool and is examined. - function testRateOnlyTransactionIsOutOfScope() public { + function retiredRateOnlyTransactionIsOutOfScope() public { _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); vault.shiftRateOnly(); } @@ -318,7 +330,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// unrelated singleton traffic is skipped without consulting the rate provider even /// when that provider is broken: the watched provider never becomes a liveness /// dependency of the rest of the singleton. - function testUnrelatedVaultTrafficIgnoresBrokenProvider() public { + function retiredUnrelatedVaultTrafficIgnoresBrokenProvider() public { rateProvider.setRate(0); // would trip "returned zero rate" if the drift loop ran _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); @@ -328,7 +340,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { /// @notice A custody imbalance that predates the transaction is flagged at the transaction /// that caused it, not re-litigated by every later unrelated transaction: the deficit /// comparison sees an unchanged baseline and lets the transaction through. - function testUnrelatedVaultTrafficSkipsPreexistingCustodyImbalance() public { + function retiredUnrelatedVaultTrafficSkipsPreexistingCustodyImbalance() public { vault.seedPoolBalance(0, 2_000e18); // pool claims exceed reserves before the armed tx _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -337,19 +349,19 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- wiring ---------------------------------------------------------------- - function testDeploys() public { + function retiredDeploys() public { BalancerV3VaultAssertion assertion = new BalancerV3VaultAssertion( address(vault), address(pool), false, INVARIANT_DUST_TOLERANCE, RATE_DRIFT_TOLERANCE_BPS ); assertTrue(address(assertion) != address(0)); } - function testRejectsZeroVault() public { + function retiredRejectsZeroVault() public { vm.expectRevert(bytes("BalancerV3: zero vault")); new BalancerV3VaultAssertion(address(0), address(pool), false, 0, RATE_DRIFT_TOLERANCE_BPS); } - function testRejectsZeroPool() public { + function retiredRejectsZeroPool() public { vm.expectRevert(bytes("BalancerV3: zero pool")); new BalancerV3VaultAssertion(address(vault), address(0), false, 0, RATE_DRIFT_TOLERANCE_BPS); } diff --git a/examples/euler/README.md b/examples/euler/README.md index 0a72714..770f07a 100644 --- a/examples/euler/README.md +++ b/examples/euler/README.md @@ -2,6 +2,10 @@ Assertion examples and supporting helpers extracted from the `eulerv2` branch. +The full `EulerEVaultAssertion` and standalone per-call share-price wrapper are unavailable and +register no triggers. Supported EVK hooks and causal debt-socialization attribution must be modeled +before activation. The user-storage accounting wrapper remains independently available. + ## Build ```sh diff --git a/examples/euler/src/EulerEVaultAssertion.sol b/examples/euler/src/EulerEVaultAssertion.sol index ba19d2c..61c08c4 100644 --- a/examples/euler/src/EulerEVaultAssertion.sol +++ b/examples/euler/src/EulerEVaultAssertion.sol @@ -315,8 +315,8 @@ contract EulerEVaultAssertion is EulerUserStorageAccountingMixin, EulerPerCallSh /// @notice Registers all EVK example assertion triggers. /// @dev Intended for factory-scoped installs where the assertion adopter is the concrete EVault. function triggers() external view override { - _registerUserStorageAccounting(); - _registerPerCallSharePrice(); + // Quarantined as a bundle. The share-price compensation is not causally tied to debt + // socialization and exact raw-balance comparisons reject supported pre-operation hooks. } } @@ -344,7 +344,7 @@ contract EulerPerCallSharePriceAssertion is EulerPerCallSharePriceMixin { /// @notice Registers EVK call-level share-price triggers. function triggers() external view override { - _registerPerCallSharePrice(); + // Quarantined for the same hook and non-causal compensation gaps as the full bundle. } } diff --git a/examples/lighter/README.md b/examples/lighter/README.md index 72242ab..c9390e8 100644 --- a/examples/lighter/README.md +++ b/examples/lighter/README.md @@ -3,6 +3,11 @@ Runtime Credible Layer assertions for Lighter's L1 bridge / rollup contract (`ZkLighter`, the proxy at `0x3B4D794a66304F130a4Db8F2551B0070dfCf5ca7` on Ethereum mainnet). +> **Unavailable:** the `LighterBridgeAssertion` wrapper is quarantined and registers no triggers. +> It must bind the current proxy implementation, causally bind root changes to the executed batch, +> and validate official request expiry before desert-mode activation. The outflow circuit breaker is +> a separate operational policy and is not represented as a bridge invariant. + Lighter is an app-specific ZK validity rollup whose single proxied contract is simultaneously the funds-custody bridge and the rollup state machine: a `committed -> verified -> executed` batch pipeline, a parallel priority-request queue for L1->L2 deposits / forced transactions, and a diff --git a/examples/lighter/src/LighterBridgeAssertion.sol b/examples/lighter/src/LighterBridgeAssertion.sol index cd21fce..9936e6e 100644 --- a/examples/lighter/src/LighterBridgeAssertion.sol +++ b/examples/lighter/src/LighterBridgeAssertion.sol @@ -31,8 +31,8 @@ contract LighterBridgeAssertion is LighterBridgeHelpers { /// @notice Registers the rollup state-machine envelope checks and the desert-mode check. /// @dev All are whole-transaction properties, so they use tx-end triggers. function triggers() external view override { - _registerStateMachineTriggers(); - registerTxEndTrigger(this.assertDesertModeIntegrity.selector); + // Quarantined: root changes must be bound to the executed batch transition, desert-mode + // activation must check request expiry, and the adapter must bind the proxy revision. } /// @notice The escape hatch is latching and freezes the operator while it is open. diff --git a/examples/lighter/test/LighterBridgeAssertion.t.sol b/examples/lighter/test/LighterBridgeAssertion.t.sol index 433ec7a..fae1118 100644 --- a/examples/lighter/test/LighterBridgeAssertion.t.sol +++ b/examples/lighter/test/LighterBridgeAssertion.t.sol @@ -173,14 +173,24 @@ contract LighterBridgeAssertionTest is Test, CredibleTest { cl.assertion(address(bridge), createData, fnSelector); } + function testPublishedWrapperCannotRegisterUnsafeChecks() public { + LighterBridgeAssertion assertion = new LighterBridgeAssertion(address(bridge)); + vm.mockCallRevert( + address(uint160(uint256(keccak256("TriggerRecorder")))), + bytes(""), + bytes("quarantined wrapper registered a trigger") + ); + assertion.triggers(); + } + // --- Batch ordering --------------------------------------------------- - function testOrderingHonestVerifyPasses() public { + function retiredOrderingHonestVerifyPasses() public { _arm(RollupBridgeStateMachineAssertion.assertBatchOrdering.selector); bridge.verify(1, 1); // verified 8->9 <= committed 10 } - function testOrderingVerifyBeyondCommittedTrips() public { + function retiredOrderingVerifyBeyondCommittedTrips() public { _arm(RollupBridgeStateMachineAssertion.assertBatchOrdering.selector); vm.expectRevert(bytes("RollupBridge: verified exceeds committed batches")); bridge.forceVerifyBeyondCommitted(); // verified -> 11 > committed 10 @@ -188,37 +198,37 @@ contract LighterBridgeAssertionTest is Test, CredibleTest { // --- Finality non-decrease -------------------------------------------- - function testFinalityHonestExecutePasses() public { + function retiredFinalityHonestExecutePasses() public { _arm(RollupBridgeStateMachineAssertion.assertFinalityNonDecreasing.selector); bridge.execute(1, 1, ROOT_B); // executed 6->7 } - function testFinalityExecutedRollbackTrips() public { + function retiredFinalityExecutedRollbackTrips() public { _arm(RollupBridgeStateMachineAssertion.assertFinalityNonDecreasing.selector); vm.expectRevert(bytes("RollupBridge: executed batches decreased")); bridge.rollbackExecutedBatch(); // executed 6->5 } - function testFinalityAllowsVerifiedRollbackAboveExecutedTip() public { + function retiredFinalityAllowsVerifiedRollbackAboveExecutedTip() public { _arm(RollupBridgeStateMachineAssertion.assertFinalityNonDecreasing.selector); bridge.rollbackVerifiedBatch(); // verified 8->7 while executed remains 6 } // --- State-root continuity -------------------------------------------- - function testStateRootHonestExecuteAdvancePasses() public { + function retiredStateRootHonestExecuteAdvancePasses() public { _arm(RollupBridgeStateMachineAssertion.assertStateRootContinuity.selector); bridge.execute(1, 1, ROOT_B); // root changes, executed advanced } - function testStateRootAuthorizedMigrationPasses() public { + function retiredStateRootAuthorizedMigrationPasses() public { _arm(RollupBridgeStateMachineAssertion.assertStateRootContinuity.selector); // Root moves with no executed advance, but through the authorized migration selector. (bool ok,) = address(bridge).call(abi.encodePacked(bytes4(0x7271277e), ROOT_B)); assertTrue(ok); } - function testStateRootRewriteWithoutExecutionTrips() public { + function retiredStateRootRewriteWithoutExecutionTrips() public { _arm(RollupBridgeStateMachineAssertion.assertStateRootContinuity.selector); vm.expectRevert(bytes("RollupBridge: state root changed without execution")); bridge.rewriteStateRoot(ROOT_B); // root changes, executed unchanged, no migration call @@ -226,39 +236,39 @@ contract LighterBridgeAssertionTest is Test, CredibleTest { // --- Desert-mode integrity -------------------------------------------- - function testDesertActivationFromActiveStatePasses() public { + function retiredDesertActivationFromActiveStatePasses() public { _arm(LighterBridgeAssertion.assertDesertModeIntegrity.selector); bridge.activateDesertMode(); // pre: not desert -> freeze checks skipped } - function testDesertActivationWithoutOpenRequestsTrips() public { + function retiredDesertActivationWithoutOpenRequestsTrips() public { bridge.seed(10, 8, 6, 6, 6, 6, 0, ROOT_A, false); _arm(LighterBridgeAssertion.assertDesertModeIntegrity.selector); vm.expectRevert(bytes("LighterBridge: desert mode activated without open requests")); bridge.activateDesertMode(); } - function testDesertCancellationConservesPriorityRequests() public { + function retiredDesertCancellationConservesPriorityRequests() public { bridge.seed(10, 8, 6, 10, 8, 6, 4, ROOT_A, true); _arm(LighterBridgeAssertion.assertDesertModeIntegrity.selector); bridge.cancelOutstandingDeposits(4); } - function testDesertPriorityAccountingCorruptionTrips() public { + function retiredDesertPriorityAccountingCorruptionTrips() public { bridge.seed(10, 8, 6, 10, 8, 6, 4, ROOT_A, true); _arm(LighterBridgeAssertion.assertDesertModeIntegrity.selector); vm.expectRevert(bytes("LighterBridge: desert cancellation does not conserve priority requests")); bridge.corruptDesertPriorityAccounting(); } - function testDesertModeExitTrips() public { + function retiredDesertModeExitTrips() public { bridge.seed(10, 8, 6, 10, 8, 6, 4, ROOT_A, true); // already in desert mode _arm(LighterBridgeAssertion.assertDesertModeIntegrity.selector); vm.expectRevert(bytes("LighterBridge: desert mode exited")); bridge.reopenOperator(); // desert true -> false } - function testDesertModeOperatorFreezeTrips() public { + function retiredDesertModeOperatorFreezeTrips() public { bridge.seed(10, 8, 6, 10, 8, 6, 4, ROOT_A, true); // already in desert mode _arm(LighterBridgeAssertion.assertDesertModeIntegrity.selector); vm.expectRevert(bytes("LighterBridge: committed advanced in desert mode")); @@ -267,7 +277,7 @@ contract LighterBridgeAssertionTest is Test, CredibleTest { // --- Constructor wiring ------------------------------------------------ - function testRejectsZeroBridge() public { + function retiredRejectsZeroBridge() public { vm.expectRevert(bytes("LighterBridge: bridge zero")); new LighterBridgeAssertion(address(0)); } diff --git a/examples/spark/README.md b/examples/spark/README.md index f36ea4a..b0b4389 100644 --- a/examples/spark/README.md +++ b/examples/spark/README.md @@ -2,6 +2,9 @@ Assertion examples and supporting helpers extracted from the `spark` branch. +`SparkLendOraclePriceGuardAssertion` is unavailable and registers no triggers until collateral +disablement, outgoing transfer finalization, and risk-increasing eMode transitions are covered. + ## Build ```sh diff --git a/examples/spark/src/SparkLendOraclePriceGuardAssertion.sol b/examples/spark/src/SparkLendOraclePriceGuardAssertion.sol index 70cb502..8bc2a65 100644 --- a/examples/spark/src/SparkLendOraclePriceGuardAssertion.sol +++ b/examples/spark/src/SparkLendOraclePriceGuardAssertion.sol @@ -102,11 +102,9 @@ contract SparkLendOraclePriceGuardAssertion is Assertion { /// @dev Supply and repay are deliberately not registered. This leaves debt repayment /// open when a watched wrapped asset depegs and risky calls enter reduce-only mode. function triggers() external view override { - registerFnCallTrigger(this.assertOraclePricesTrackMarket.selector, IAaveV3LikePool.borrow.selector); - registerFnCallTrigger(this.assertOraclePricesTrackMarket.selector, IAaveV3LikePool.withdraw.selector); - // Liquidations are deliberately not gated: rejecting them during a depeg prevents the - // permissionless risk-reducing path this policy is meant to preserve. eMode needs an - // old/new-category-aware adapter so exits and no-op selections remain available. + // Quarantined: the current surface misses collateral-disable, outgoing finalizeTransfer, + // and risk-increasing eMode transitions. Partial oracle gating must not ship as complete + // protection; liquidation remains deliberately available. } /// @notice Checks touched watched assets against off-chain market reference prices. diff --git a/examples/symbiotic/README.md b/examples/symbiotic/README.md index e0c987b..d1ac891 100644 --- a/examples/symbiotic/README.md +++ b/examples/symbiotic/README.md @@ -10,8 +10,9 @@ FOUNDRY_PROFILE=symbiotic forge build ## Files -The ready-to-use vault bundle now arms only call-scoped v1 accounting checks, including claimant -entitlement, active-share ownership, and slashing bucket conservation. The generic rolling-flow +The ready-to-use vault bundle and standalone flow wrapper are quarantined and register no triggers. +They must support valid self-recipient and fee-on-transfer behavior, scale batch claims within the +assertion gas budget, and bind an official v1 deployment before activation. The generic rolling-flow breaker, recommended configuration bundle, and relay bundle register no triggers because their original observations could reject valid protocol configurations or accept unrelated escape calls. The custom configuration contract remains an explicit operator policy and must be calibrated for diff --git a/examples/symbiotic/src/SymbioticVaultAssertion.sol b/examples/symbiotic/src/SymbioticVaultAssertion.sol index 9e4f3c5..e628be0 100644 --- a/examples/symbiotic/src/SymbioticVaultAssertion.sol +++ b/examples/symbiotic/src/SymbioticVaultAssertion.sol @@ -41,6 +41,7 @@ contract SymbioticVaultAssertion is /// @notice Wires the v1 call-scoped accounting checks. function triggers() external view override { - _registerVaultFlowTriggers(); + // Quarantined until v1 identity is pinned and self-recipient/fee-on-transfer claims plus + // batch scaling are represented without false positives or assertion gas exhaustion. } } diff --git a/examples/symbiotic/src/SymbioticVaultFlowAssertion.sol b/examples/symbiotic/src/SymbioticVaultFlowAssertion.sol index 2a3a192..c25b21b 100644 --- a/examples/symbiotic/src/SymbioticVaultFlowAssertion.sol +++ b/examples/symbiotic/src/SymbioticVaultFlowAssertion.sol @@ -256,6 +256,7 @@ contract SymbioticVaultProtection is SymbioticVaultFlowAssertion { /// in v1 `totalStake()` is defined from the same bucket getters the per-call checks /// already read, so a tx-end reread is redundant. function triggers() external view override { - _registerVaultFlowTriggers(); + // Quarantined with the published bundle; direct function bodies remain for redesign and + // regression development but this wrapper cannot arm them. } } diff --git a/examples/symbiotic/test/SymbioticVaultFlowAssertion.t.sol b/examples/symbiotic/test/SymbioticVaultFlowAssertion.t.sol index 3d04b59..b6c2251 100644 --- a/examples/symbiotic/test/SymbioticVaultFlowAssertion.t.sol +++ b/examples/symbiotic/test/SymbioticVaultFlowAssertion.t.sol @@ -119,13 +119,23 @@ contract SymbioticVaultFlowAssertionTest is Test, CredibleTest { cl.assertion(address(vault), createData, selector); } - function testClaimPaysFullEntitlement() public { + function testPublishedWrapperCannotRegisterUnsafeChecks() public { + SymbioticVaultProtection assertion = new SymbioticVaultProtection(address(vault), address(asset)); + vm.mockCallRevert( + address(uint160(uint256(keccak256("TriggerRecorder")))), + bytes(""), + bytes("quarantined wrapper registered a trigger") + ); + assertion.triggers(); + } + + function retiredClaimPaysFullEntitlement() public { vault.seedClaim(1, address(this), 100 ether, 100 ether); _arm(SymbioticVaultFlowAssertion.assertClaimFlow.selector); vault.claim(recipient, 1); } - function testUnderpaidClaimCannotConsumeEpoch() public { + function retiredUnderpaidClaimCannotConsumeEpoch() public { vault.seedClaim(1, address(this), 100 ether, 100 ether); vault.setUnderpayClaim(true); _arm(SymbioticVaultFlowAssertion.assertClaimFlow.selector); @@ -133,7 +143,7 @@ contract SymbioticVaultFlowAssertionTest is Test, CredibleTest { vault.claim(recipient, 1); } - function testClaimBatchPaysEachMatureEpochOnce() public { + function retiredClaimBatchPaysEachMatureEpochOnce() public { vault.seedClaim(0, address(this), 40 ether, 40 ether); vault.seedClaim(1, address(this), 100 ether, 100 ether); _arm(SymbioticVaultFlowAssertion.assertClaimBatchFlow.selector); @@ -143,7 +153,7 @@ contract SymbioticVaultFlowAssertionTest is Test, CredibleTest { vault.claimBatch(recipient, epochs); } - function testClaimBatchRejectsRepeatedEpoch() public { + function retiredClaimBatchRejectsRepeatedEpoch() public { vault.seedClaim(1, address(this), 100 ether, 100 ether); vault.setPayDuplicateEpochs(true); _arm(SymbioticVaultFlowAssertion.assertClaimBatchFlow.selector); @@ -154,14 +164,14 @@ contract SymbioticVaultFlowAssertionTest is Test, CredibleTest { vault.claimBatch(recipient, epochs); } - function testCurrentEpochSlashConservesBucketsAndPaysBurner() public { + function retiredCurrentEpochSlashConservesBucketsAndPaysBurner() public { vault.seedSlash(100 ether, 30 ether, 20 ether); _arm(SymbioticVaultFlowAssertion.assertSlashAccounting.selector); vm.prank(slasher); vault.onSlash(60 ether, 250); } - function testCorruptSlashBucketAccountingTrips() public { + function retiredCorruptSlashBucketAccountingTrips() public { vault.seedSlash(100 ether, 30 ether, 20 ether); vault.setCorruptSlashBuckets(true); _arm(SymbioticVaultFlowAssertion.assertSlashAccounting.selector);