From 3e756e91f656f7cceb14ac57c3c8f1d0c26ec3b1 Mon Sep 17 00:00:00 2001 From: makemake Date: Fri, 7 Aug 2026 17:49:26 +0200 Subject: [PATCH 1/2] fix(balancer): align std example with active release --- examples/balancer/README.md | 5 -- .../balancer/src/BalancerV3VaultAssertion.sol | 17 ++++- .../test/BalancerV3VaultAssertion.t.sol | 72 ++++++++----------- 3 files changed, 45 insertions(+), 49 deletions(-) diff --git a/examples/balancer/README.md b/examples/balancer/README.md index b44f0d6..87e4dad 100644 --- a/examples/balancer/README.md +++ b/examples/balancer/README.md @@ -2,11 +2,6 @@ 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 d180089..3d84729 100644 --- a/examples/balancer/src/BalancerV3VaultAssertion.sol +++ b/examples/balancer/src/BalancerV3VaultAssertion.sol @@ -60,8 +60,21 @@ 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 { - // 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. + 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); } /// @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 ddb0f18..efb74ec 100644 --- a/examples/balancer/test/BalancerV3VaultAssertion.t.sol +++ b/examples/balancer/test/BalancerV3VaultAssertion.t.sol @@ -56,18 +56,6 @@ 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, @@ -104,7 +92,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- assertSwapPreservesPoolInvariant ------------------------------------ - function retiredHonestSwapPassesInvariantAssertion() public { + function testHonestSwapPassesInvariantAssertion() public { _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); _swap(address(pool)); } @@ -112,21 +100,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 retiredHonestSwapWithPendingYieldFeesPassesInvariantAssertion() public { + function testHonestSwapWithPendingYieldFeesPassesInvariantAssertion() public { vault.seedPendingYieldFee(address(token0), 20e18); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); _swap(address(pool)); } - function retiredSwapOnUnwatchedPoolIsIgnored() public { + function testSwapOnUnwatchedPoolIsIgnored() public { vault.setMode(MockBalancerV3Vault.Mode.InvariantLoss); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); _swap(makeAddr("otherPool")); } - function retiredInvariantLossTrips() public { + function testInvariantLossTrips() public { vault.setMode(MockBalancerV3Vault.Mode.InvariantLoss); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); @@ -134,7 +122,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { _swap(address(pool)); } - function retiredSupplyDriftTrips() public { + function testSupplyDriftTrips() public { vault.setMode(MockBalancerV3Vault.Mode.SupplyDrift); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); @@ -142,7 +130,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { _swap(address(pool)); } - function retiredBalancesAgainstSwapDirectionTrip() public { + function testBalancesAgainstSwapDirectionTrip() public { vault.setMode(MockBalancerV3Vault.Mode.BalanceSwapEnds); _arm(BalancerV3VaultAssertion.assertSwapPreservesPoolInvariant.selector); @@ -154,7 +142,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 retiredHookedPoolSwapChecksAreSkipped() public { + function testHookedPoolSwapChecksAreSkipped() public { vault.setSwapHooks(true); vault.setMode(MockBalancerV3Vault.Mode.InvariantLoss); @@ -166,7 +154,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 retiredTransientRateManipulationAroundSwapTrips() public { + function testTransientRateManipulationAroundSwapTrips() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); token0.mint(address(router), 100e18); router.approveVault(address(token0)); @@ -179,7 +167,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 retiredTransientRateManipulationPassesEndpointDrift() public { + function testTransientRateManipulationPassesEndpointDrift() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); token0.mint(address(router), 100e18); router.approveVault(address(token0)); @@ -188,7 +176,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { router.manipulateSwapRestore(_swapParams(address(pool))); } - function retiredTransientRateManipulationAroundAddLiquidityTrips() public { + function testTransientRateManipulationAroundAddLiquidityTrips() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); @@ -196,12 +184,12 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { router.manipulateAddLiquidityRestore(_addLiquidityParams(address(pool))); } - function retiredHonestAddLiquidityPassesScopedRateAssertion() public { + function testHonestAddLiquidityPassesScopedRateAssertion() public { _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); vault.addLiquidity(_addLiquidityParams(address(pool))); } - function retiredTransientRateManipulationAroundRemoveLiquidityTrips() public { + function testTransientRateManipulationAroundRemoveLiquidityTrips() public { RateManipulatingRouter router = new RateManipulatingRouter(vault, rateProvider); _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); @@ -211,12 +199,12 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- assertPoolAccountingWithinVaultCustody ------------------------------- - function retiredHonestSwapPassesCustodyAssertion() public { + function testHonestSwapPassesCustodyAssertion() public { _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); _swap(address(pool)); } - function retiredReserveSkimTripsCustody() public { + function testReserveSkimTripsCustody() public { vault.setMode(MockBalancerV3Vault.Mode.ReserveSkim); _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -224,7 +212,7 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { _swap(address(pool)); } - function retiredPhantomPoolBalanceTripsCustody() public { + function testPhantomPoolBalanceTripsCustody() public { vault.setMode(MockBalancerV3Vault.Mode.PhantomBalance); _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -236,7 +224,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 retiredCustodyDrainWithoutAccountingChangeTrips() public { + function testCustodyDrainWithoutAccountingChangeTrips() public { _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); vm.expectRevert(bytes("BalancerV3: vault reserves exceed real token custody")); vault.sweepCustodyOnly(address(token0), 10e18); @@ -244,7 +232,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 retiredWorseningPreexistingDeficitTrips() public { + function testWorseningPreexistingDeficitTrips() public { vault.seedPoolBalance(1, 1_600e18); // claim already exceeds reserves before arming vault.setMode(MockBalancerV3Vault.Mode.PhantomBalance); @@ -255,7 +243,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 retiredHonestOperationOnDeficientPoolPasses() public { + function testHonestOperationOnDeficientPoolPasses() public { vault.seedPoolBalance(0, 2_000e18); // claim exceeds reserves before arming _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -264,12 +252,12 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- assertTokenRatesWithinDriftBound -------------------------------------- - function retiredHonestSwapPassesRateAssertion() public { + function testHonestSwapPassesRateAssertion() public { _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); _swap(address(pool)); } - function retiredRateShiftTrips() public { + function testRateShiftTrips() public { vault.setMode(MockBalancerV3Vault.Mode.RateShift); _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); @@ -280,7 +268,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 retiredZeroBaselineForRegisteredProviderTrips() public { + function testZeroBaselineForRegisteredProviderTrips() public { rateProvider.setRate(0); vault.setMode(MockBalancerV3Vault.Mode.RateShift); // 0 -> 1e18 during the swap @@ -292,14 +280,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 retiredProviderRegisteredDuringTxIsExempt() public { + function testProviderRegisteredDuringTxIsExempt() public { vault.registerNewRateProvider(); _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); vault.initialize(address(pool), address(this), new address[](0), new uint256[](0), 0, ""); } - function retiredProviderRegisteredDuringTxPassesScopedInitializationRateCheck() public { + function testProviderRegisteredDuringTxPassesScopedInitializationRateCheck() public { vault.registerNewRateProvider(); _arm(BalancerV3VaultAssertion.assertOperationRatesWithinBaseline.selector); @@ -309,7 +297,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 retiredRecoveryModeSkipsRateAssertion() public { + function testRecoveryModeSkipsRateAssertion() public { vault.setRecoveryMode(true); vault.setMode(MockBalancerV3Vault.Mode.RateShift); @@ -321,7 +309,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 retiredRateOnlyTransactionIsOutOfScope() public { + function testRateOnlyTransactionIsOutOfScope() public { _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); vault.shiftRateOnly(); } @@ -330,7 +318,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 retiredUnrelatedVaultTrafficIgnoresBrokenProvider() public { + function testUnrelatedVaultTrafficIgnoresBrokenProvider() public { rateProvider.setRate(0); // would trip "returned zero rate" if the drift loop ran _arm(BalancerV3VaultAssertion.assertTokenRatesWithinDriftBound.selector); @@ -340,7 +328,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 retiredUnrelatedVaultTrafficSkipsPreexistingCustodyImbalance() public { + function testUnrelatedVaultTrafficSkipsPreexistingCustodyImbalance() public { vault.seedPoolBalance(0, 2_000e18); // pool claims exceed reserves before the armed tx _arm(BalancerV3VaultAssertion.assertPoolAccountingWithinVaultCustody.selector); @@ -349,19 +337,19 @@ contract BalancerV3VaultAssertionTest is Test, CredibleTest { // --- wiring ---------------------------------------------------------------- - function retiredDeploys() public { + function testDeploys() public { BalancerV3VaultAssertion assertion = new BalancerV3VaultAssertion( address(vault), address(pool), false, INVARIANT_DUST_TOLERANCE, RATE_DRIFT_TOLERANCE_BPS ); assertTrue(address(assertion) != address(0)); } - function retiredRejectsZeroVault() public { + function testRejectsZeroVault() public { vm.expectRevert(bytes("BalancerV3: zero vault")); new BalancerV3VaultAssertion(address(0), address(pool), false, 0, RATE_DRIFT_TOLERANCE_BPS); } - function retiredRejectsZeroPool() public { + function testRejectsZeroPool() public { vm.expectRevert(bytes("BalancerV3: zero pool")); new BalancerV3VaultAssertion(address(vault), address(0), false, 0, RATE_DRIFT_TOLERANCE_BPS); } From a097fe1df5f733b0b3afcc404068146c8340cc6f Mon Sep 17 00:00:00 2001 From: makemake Date: Fri, 7 Aug 2026 17:56:08 +0200 Subject: [PATCH 2/2] fix(ci): remove retired Safe guard job --- .github/workflows/solidity-test.yml | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/.github/workflows/solidity-test.yml b/.github/workflows/solidity-test.yml index e3c6666..8f932f8 100644 --- a/.github/workflows/solidity-test.yml +++ b/.github/workflows/solidity-test.yml @@ -96,19 +96,3 @@ jobs: exit 1 fi echo "Verified ${assertion_count} assertion-running test case(s) across ${testcase_count} tests" - - # Real Gnosis Safe compatibility and deployment tooling use the legacy compiler pipeline and - # plain Forge intentionally; they do not depend on Credible assertion cheatcodes. - safe-guard-integration: - runs-on: ubuntu-latest - steps: - - name: Checkout - uses: actions/checkout@v6 - with: - submodules: recursive - - - name: Install Foundry - uses: foundry-rs/foundry-toolchain@v1 - - - name: Run Safe guard compatibility and script tests - run: FOUNDRY_PROFILE=safe-guard forge test