diff --git a/examples/safe-guard/README.md b/examples/safe-guard/README.md index 88c6dfb..fdb12d6 100644 --- a/examples/safe-guard/README.md +++ b/examples/safe-guard/README.md @@ -21,12 +21,13 @@ These integration tests live under their own profile because the Safe contracts ## Deploy the guard -The deployment script reads the registry and fail-open threshold from the environment. Foundry handles the deployer wallet; the script does not read a private key. +The deployment script reads the registry, fail-open threshold, and initial protocol manager from the environment. Foundry handles the deployer wallet; the script does not read a private key. ```sh FOUNDRY_PROFILE=safe-guard \ CREDIBLE_REGISTRY=0x... \ FAIL_OPEN_BLOCK_THRESHOLD=75 \ +INITIAL_PROTOCOL_MANAGER=0x... \ forge script examples/safe-guard/script/DeployCredibleSafeGuard.s.sol \ --rpc-url "$RPC_URL" \ --account \ diff --git a/examples/safe-guard/script/DeployCredibleSafeGuard.s.sol b/examples/safe-guard/script/DeployCredibleSafeGuard.s.sol index be51bf0..d7c8149 100644 --- a/examples/safe-guard/script/DeployCredibleSafeGuard.s.sol +++ b/examples/safe-guard/script/DeployCredibleSafeGuard.s.sol @@ -19,6 +19,7 @@ contract DeployCredibleSafeGuard is Script { function run() external returns (CredibleSafeGuard guard) { address registry = vm.envAddress("CREDIBLE_REGISTRY"); uint256 threshold = vm.envUint("FAIL_OPEN_BLOCK_THRESHOLD"); + address protocolManager = vm.envAddress("INITIAL_PROTOCOL_MANAGER"); // Validate the registry before broadcasting so a misconfigured (codeless or // non-responsive) registry is caught up front rather than silently deploying a @@ -26,17 +27,21 @@ contract DeployCredibleSafeGuard is Script { validateRegistry(registry); vm.startBroadcast(); - guard = deploy(registry, threshold); + guard = deploy(registry, threshold, protocolManager); vm.stopBroadcast(); console2.log("Chain ID:", block.chainid); console2.log("Credible Safe guard:", address(guard)); console2.log("Credible Registry:", registry); console2.log("Fail-open block threshold:", threshold); + console2.log("Initial protocol manager:", protocolManager); } - function deploy(address registry, uint256 threshold) public returns (CredibleSafeGuard) { - return new CredibleSafeGuard(ICredibleRegistry(registry), threshold); + function deploy(address registry, uint256 threshold, address protocolManager) + public + returns (CredibleSafeGuard) + { + return new CredibleSafeGuard(ICredibleRegistry(registry), threshold, protocolManager); } /// @notice Asserts the registry has code and answers both reads the guard depends on. diff --git a/examples/safe-guard/test/CredibleSafeGuardSafeIntegration.t.sol b/examples/safe-guard/test/CredibleSafeGuardSafeIntegration.t.sol index 9cea8ed..e95fd3f 100644 --- a/examples/safe-guard/test/CredibleSafeGuardSafeIntegration.t.sol +++ b/examples/safe-guard/test/CredibleSafeGuardSafeIntegration.t.sol @@ -20,6 +20,7 @@ contract CredibleSafeGuardSafeIntegrationTest is Test { uint256 internal constant THRESHOLD = 75; uint256 internal constant BASE_BLOCK = 1_000_000; + address internal constant PROTOCOL_MANAGER = address(0xA11CE); uint256 internal ownerPk = uint256(keccak256("safe.owner")); address internal owner; @@ -32,7 +33,7 @@ contract CredibleSafeGuardSafeIntegrationTest is Test { owner = vm.addr(ownerPk); registry = new CredibleRegistryMock(); - guard = new CredibleSafeGuard(registry, THRESHOLD); + guard = new CredibleSafeGuard(registry, THRESHOLD, PROTOCOL_MANAGER); Safe singleton = new Safe(); SafeProxyFactory factory = new SafeProxyFactory(); diff --git a/examples/safe-guard/test/CredibleSafeGuardScripts.t.sol b/examples/safe-guard/test/CredibleSafeGuardScripts.t.sol index e60ddff..7f18326 100644 --- a/examples/safe-guard/test/CredibleSafeGuardScripts.t.sol +++ b/examples/safe-guard/test/CredibleSafeGuardScripts.t.sol @@ -8,6 +8,8 @@ import {SafeProxyFactory} from "../../../lib/safe-smart-account/contracts/proxie import {Enum} from "../../../lib/safe-smart-account/contracts/common/Enum.sol"; import {CredibleSafeGuard} from "credible-std/protection/safe/CredibleSafeGuard.sol"; +import {InitialProtocolManager} from + "credible-std/protection/credible_block/InitialProtocolManager.sol"; import {CredibleRegistryMock} from "../src/CredibleRegistryMock.sol"; import {DeployCredibleSafeGuard} from "../script/DeployCredibleSafeGuard.s.sol"; import {GenerateSafeGuardBatch} from "../script/GenerateSafeGuardBatch.s.sol"; @@ -39,6 +41,7 @@ contract CredibleSafeGuardScriptsTest is Test { uint256 internal constant THRESHOLD = 75; uint256 internal constant CREATED_AT = 1_700_000_000_000; + address internal constant PROTOCOL_MANAGER = address(0xA11CE); uint256 internal ownerPk = uint256(keccak256("safe.owner")); address internal owner; @@ -66,18 +69,22 @@ contract CredibleSafeGuardScriptsTest is Test { } function test_deployHelper_deploysConfiguredGuard() public { - CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD); + CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD, PROTOCOL_MANAGER); assertEq(address(guard.credibleRegistry()), address(registry)); assertEq(guard.failOpenBlockThreshold(), THRESHOLD); + assertEq(guard.initialProtocolManager(), PROTOCOL_MANAGER); } function test_deployHelper_preservesConstructorValidation() public { vm.expectRevert(CredibleSafeGuard.ZeroCredibleRegistryAddress.selector); - deployer.deploy(address(0), THRESHOLD); + deployer.deploy(address(0), THRESHOLD, PROTOCOL_MANAGER); vm.expectRevert(CredibleSafeGuard.ZeroFailOpenBlockThreshold.selector); - deployer.deploy(address(registry), 0); + deployer.deploy(address(registry), 0, PROTOCOL_MANAGER); + + vm.expectRevert(InitialProtocolManager.ZeroInitialProtocolManager.selector); + deployer.deploy(address(registry), THRESHOLD, address(0)); } function test_validateRegistry_acceptsResponsiveRegistry() public view { @@ -118,7 +125,7 @@ contract CredibleSafeGuardScriptsTest is Test { } function test_installBatch_matchesSafeTransactionBuilderSchema() public { - CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD); + CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD, PROTOCOL_MANAGER); string memory json = generator.buildInstallBatch(address(safe), address(guard), block.chainid, CREATED_AT); assertEq(vm.parseJsonString(json, ".version"), "1.0"); @@ -158,7 +165,7 @@ contract CredibleSafeGuardScriptsTest is Test { } function test_generatedInstallBatch_executesThroughRealSafe() public { - CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD); + CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD, PROTOCOL_MANAGER); string memory json = generator.buildInstallBatch(address(safe), address(guard), block.chainid, CREATED_AT); _executeGeneratedBatch(json); @@ -166,8 +173,8 @@ contract CredibleSafeGuardScriptsTest is Test { } function test_generatedBatch_replacesAndRemovesGuard() public { - CredibleSafeGuard firstGuard = deployer.deploy(address(registry), THRESHOLD); - CredibleSafeGuard secondGuard = deployer.deploy(address(registry), THRESHOLD + 1); + CredibleSafeGuard firstGuard = deployer.deploy(address(registry), THRESHOLD, PROTOCOL_MANAGER); + CredibleSafeGuard secondGuard = deployer.deploy(address(registry), THRESHOLD + 1, PROTOCOL_MANAGER); _executeGeneratedBatch( generator.buildInstallBatch(address(safe), address(firstGuard), block.chainid, CREATED_AT) @@ -186,7 +193,7 @@ contract CredibleSafeGuardScriptsTest is Test { } function test_rejectsInvalidSafeAndGuardInputs() public { - CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD); + CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD, PROTOCOL_MANAGER); vm.expectRevert(SafeGuardBatch.ZeroSafeAddress.selector); generator.buildInstallBatch(address(0), address(guard), block.chainid, CREATED_AT); @@ -216,7 +223,7 @@ contract CredibleSafeGuardScriptsTest is Test { } function test_run_writesImportableInstallBatch() public { - CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD); + CredibleSafeGuard guard = deployer.deploy(address(registry), THRESHOLD, PROTOCOL_MANAGER); vm.setEnv("SAFE_ADDRESS", vm.toString(address(safe))); vm.setEnv("SAFE_GUARD_ACTION", "install"); vm.setEnv("CREDIBLE_SAFE_GUARD", vm.toString(address(guard))); diff --git a/src/protection/credible_block/IInitialProtocolManager.sol b/src/protection/credible_block/IInitialProtocolManager.sol new file mode 100644 index 0000000..a7daca0 --- /dev/null +++ b/src/protection/credible_block/IInitialProtocolManager.sol @@ -0,0 +1,24 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +/// @title IInitialProtocolManager +/// @author Phylax Systems +/// @notice Interface a protected contract exposes to declare the address allowed to manage its +/// assertions in the Credible Layer. Every protected contract needs a protocol manager; +/// exposing the intended manager on the contract itself lets the state oracle set it +/// without a manual review round. +/// @dev The state oracle calls {initialProtocolManager} on the contract when the protocol is +/// initialized in the Credible Layer and registers the returned address as the manager. +/// Because the value is defined by the contract's own code, deploying the contract is the +/// ownership proof: whoever controlled the deployment chose the manager. This is what makes +/// updated or redeployed contracts self-verifying, with no separate claim step. +/// +/// Implementing this interface is optional. Contracts that do not expose it (for example, +/// already-deployed contracts that cannot be changed) go through manual verification, where +/// Phylax confirms ownership directly and sets the manager. +interface IInitialProtocolManager { + /// @notice The address to set as this contract's protocol manager when the protocol is + /// initialized in the Credible Layer. + /// @return The intended initial protocol manager address. + function initialProtocolManager() external view returns (address); +} diff --git a/src/protection/credible_block/InitialProtocolManager.sol b/src/protection/credible_block/InitialProtocolManager.sol new file mode 100644 index 0000000..46e69ff --- /dev/null +++ b/src/protection/credible_block/InitialProtocolManager.sol @@ -0,0 +1,37 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {IInitialProtocolManager} from "./IInitialProtocolManager.sol"; + +/// @title InitialProtocolManager +/// @author Phylax Systems +/// @notice Reusable base that implements {IInitialProtocolManager} by fixing the intended manager +/// at deployment. Inherit it and forward the manager address to the constructor; the +/// public immutable satisfies the interface's {initialProtocolManager} getter. +/// @dev The manager is immutable, so the value the state oracle reads is exactly what the deployer +/// committed to in the deployment transaction — that is the ownership proof. Changing the +/// declared manager after deployment means redeploying the inheriting contract. Once the +/// protocol is initialized in the Credible Layer, the manager is managed there rather than +/// through this value. +/// +/// Example: +/// ```solidity +/// contract MyProtectedContract is InitialProtocolManager { +/// constructor(address manager) InitialProtocolManager(manager) {} +/// } +/// ``` +abstract contract InitialProtocolManager is IInitialProtocolManager { + /// @inheritdoc IInitialProtocolManager + address public immutable initialProtocolManager; + + /// @notice Thrown when constructed with the zero address as the initial protocol manager. + error ZeroInitialProtocolManager(); + + /// @param initialProtocolManager_ The address to declare as this contract's initial protocol + /// manager. Must be non-zero. + constructor(address initialProtocolManager_) { + if (initialProtocolManager_ == address(0)) revert ZeroInitialProtocolManager(); + + initialProtocolManager = initialProtocolManager_; + } +} diff --git a/src/protection/credible_block/README.md b/src/protection/credible_block/README.md index 63dcd4b..5db76bb 100644 --- a/src/protection/credible_block/README.md +++ b/src/protection/credible_block/README.md @@ -14,6 +14,11 @@ guard; inherit `CredibleBlockGuard` directly when you want to protect your own f - `ICredibleRegistry.sol` — read interface for the on-chain Credible Registry (`isCredibleBlock(blockNumber)`, `lastCredibleBlock()`), mirroring `phylaxsystems/credible-registry`. - `CredibleBlockGuard.sol` — abstract base contract providing the `onlyCredibleBlock` modifier. +- `IInitialProtocolManager.sol` — interface the state oracle reads to discover a contract's intended + protocol manager (`initialProtocolManager()`). See + [Initial protocol manager](#initial-protocol-manager). +- `InitialProtocolManager.sol` — abstract base implementing that interface with an immutable set at + deployment. ## Usage @@ -62,8 +67,52 @@ approximating the chain's 15-minute budget: Both the registry address and the threshold are immutable (configurable per deployment); re-pointing or re-tuning means redeploying the inheriting contract. +## Initial protocol manager + +Every protected contract needs a **protocol manager**: the address allowed to manage that +contract's assertions in the Credible Layer. Exposing the intended manager on the contract itself +lets the Credible Layer state oracle set it automatically, with no manual review round. + +Because the address is defined by the contract's own code, **deploying the contract is the +ownership proof** — whoever controlled the deployment chose the manager. This is what makes updated +or redeployed contracts self-verifying: the state oracle calls `initialProtocolManager()` on the +new contract and registers the returned address, with no separate claim step. + +Implementing this interface is **optional**. Contracts that don't expose it (for example, +already-deployed contracts you can't change) go through manual verification instead, where Phylax +confirms ownership directly and sets the manager. + +Inherit the abstract base and forward the manager address to the constructor: + +```solidity +import {InitialProtocolManager} from "credible-std/protection/credible_block/InitialProtocolManager.sol"; + +contract MyProtectedContract is InitialProtocolManager { + constructor(address manager) InitialProtocolManager(manager) {} +} +``` + +The public immutable auto-generates the `initialProtocolManager()` getter that satisfies the +interface. The manager is immutable, so the value the state oracle reads is exactly what the +deployer committed to; changing it means redeploying. Once the protocol is initialized in the +Credible Layer, the manager is managed there rather than through this value. + +Contracts that only need to declare the manager (without the zero-address check or a base +constructor) can implement `IInitialProtocolManager` directly instead: + +```solidity +import {IInitialProtocolManager} from "credible-std/protection/credible_block/IInitialProtocolManager.sol"; + +contract MyProtectedContract is IInitialProtocolManager { + function initialProtocolManager() external view returns (address) { + return 0xBEEF...; + } +} +``` + ## Tests ```sh forge test --match-path "test/protection/credible_block/CredibleBlockGuard.t.sol" +forge test --match-path "test/protection/credible_block/InitialProtocolManager.t.sol" ``` diff --git a/src/protection/safe/CredibleSafeGuard.sol b/src/protection/safe/CredibleSafeGuard.sol index f5c6c89..01185c6 100644 --- a/src/protection/safe/CredibleSafeGuard.sol +++ b/src/protection/safe/CredibleSafeGuard.sol @@ -2,6 +2,7 @@ pragma solidity ^0.8.13; import {ICredibleRegistry} from "./ICredibleRegistry.sol"; +import {InitialProtocolManager} from "../credible_block/InitialProtocolManager.sol"; /// @notice Minimal subset of Safe's `Enum` library, vendored so the guard does not /// depend on the Safe contracts package. @@ -103,7 +104,12 @@ interface ITransactionGuard is IERC165 { /// - ~1s blocks: 15 min ~= 900 blocks /// Both the registry address and the fail-open threshold are immutable; re-pointing or /// re-tuning means deploying a new guard and calling `setGuard` again. -contract CredibleSafeGuard is ITransactionGuard { +/// +/// Onboarding. The guard also implements {IInitialProtocolManager} (via {InitialProtocolManager}), +/// exposing the address allowed to manage this deployment's assertions in the Credible Layer. +/// The state oracle reads it when the protocol is initialized, so deploying the guard is itself +/// the ownership proof — no separate manual review round. +contract CredibleSafeGuard is ITransactionGuard, InitialProtocolManager { /// @dev Bounds the gas and returndata exposure of each registry probe. A registry that cannot /// answer within this budget is treated as unavailable and the guard fails open. uint256 internal constant REGISTRY_READ_GAS_LIMIT = 50_000; @@ -124,7 +130,11 @@ contract CredibleSafeGuard is ITransactionGuard { /// @param credibleRegistry_ The Credible Registry address (configurable per deployment). /// @param failOpenBlockThreshold_ Blocks of builder silence tolerated before failing open. - constructor(ICredibleRegistry credibleRegistry_, uint256 failOpenBlockThreshold_) { + /// @param initialProtocolManager_ Address allowed to manage this deployment's assertions in the + /// Credible Layer, exposed via {initialProtocolManager}. Must be non-zero. + constructor(ICredibleRegistry credibleRegistry_, uint256 failOpenBlockThreshold_, address initialProtocolManager_) + InitialProtocolManager(initialProtocolManager_) + { if (address(credibleRegistry_) == address(0)) revert ZeroCredibleRegistryAddress(); if (failOpenBlockThreshold_ == 0) revert ZeroFailOpenBlockThreshold(); diff --git a/test/protection/credible_block/InitialProtocolManager.t.sol b/test/protection/credible_block/InitialProtocolManager.t.sol new file mode 100644 index 0000000..7c8b64f --- /dev/null +++ b/test/protection/credible_block/InitialProtocolManager.t.sol @@ -0,0 +1,53 @@ +// SPDX-License-Identifier: MIT +pragma solidity ^0.8.13; + +import {Test} from "forge-std/Test.sol"; + +import {IInitialProtocolManager} from "../../../src/protection/credible_block/IInitialProtocolManager.sol"; +import {InitialProtocolManager} from "../../../src/protection/credible_block/InitialProtocolManager.sol"; + +/// @notice Minimal concrete contract that inherits the abstract base by forwarding the manager +/// address to its constructor, mirroring the intended inherit-and-forward usage. +contract ProtectedContract is InitialProtocolManager { + constructor(address manager) InitialProtocolManager(manager) {} +} + +contract InitialProtocolManagerTest is Test { + address internal constant MANAGER = address(0xA11CE); + + ProtectedContract internal protectedContract; + + function setUp() public { + protectedContract = new ProtectedContract(MANAGER); + } + + // --------------------------------------------------------------------- + // Construction + // --------------------------------------------------------------------- + + function test_constructor_storesManager() public view { + assertEq(protectedContract.initialProtocolManager(), MANAGER); + } + + function test_constructor_revertsOnZeroManager() public { + vm.expectRevert(InitialProtocolManager.ZeroInitialProtocolManager.selector); + new ProtectedContract(address(0)); + } + + function testFuzz_constructor_storesAnyNonZeroManager(address manager) public { + vm.assume(manager != address(0)); + ProtectedContract instance = new ProtectedContract(manager); + assertEq(instance.initialProtocolManager(), manager); + } + + // --------------------------------------------------------------------- + // Interface conformance + // --------------------------------------------------------------------- + + /// @dev The state oracle reads the manager through {IInitialProtocolManager}, so the public + /// immutable must satisfy that interface's getter when called through the interface type. + function test_conformsToInterface() public view { + IInitialProtocolManager asInterface = IInitialProtocolManager(address(protectedContract)); + assertEq(asInterface.initialProtocolManager(), MANAGER); + } +} diff --git a/test/protection/safe/CredibleSafeGuard.t.sol b/test/protection/safe/CredibleSafeGuard.t.sol index 92677f8..b625a81 100644 --- a/test/protection/safe/CredibleSafeGuard.t.sol +++ b/test/protection/safe/CredibleSafeGuard.t.sol @@ -5,6 +5,8 @@ import {Test} from "forge-std/Test.sol"; import {ICredibleRegistry} from "../../../src/protection/safe/ICredibleRegistry.sol"; import {CredibleSafeGuard, Enum, IERC165, ITransactionGuard} from "../../../src/protection/safe/CredibleSafeGuard.sol"; +import {InitialProtocolManager} from "../../../src/protection/credible_block/InitialProtocolManager.sol"; +import {IInitialProtocolManager} from "../../../src/protection/credible_block/IInitialProtocolManager.sol"; /// @notice Test double for the Credible Registry. Exposes fine-grained setters and a faithful /// `markCurrentBlockCredible()` replicating `phylaxsystems/credible-registry` semantics. @@ -114,13 +116,14 @@ contract CredibleSafeGuardTest is Test { /// @dev ~15 minutes of 12s blocks; chosen so boundaries are easy to reason about. uint256 internal constant THRESHOLD = 75; uint256 internal constant BASE_BLOCK = 1_000_000; + address internal constant PROTOCOL_MANAGER = address(0xA11CE); MockCredibleRegistry internal registry; CredibleSafeGuard internal guard; function setUp() public { registry = new MockCredibleRegistry(); - guard = new CredibleSafeGuard(registry, THRESHOLD); + guard = new CredibleSafeGuard(registry, THRESHOLD, PROTOCOL_MANAGER); vm.roll(BASE_BLOCK); } @@ -152,16 +155,66 @@ contract CredibleSafeGuardTest is Test { function test_constructor_storesArgs() public view { assertEq(address(guard.credibleRegistry()), address(registry)); assertEq(guard.failOpenBlockThreshold(), THRESHOLD); + assertEq(guard.initialProtocolManager(), PROTOCOL_MANAGER); } function test_constructor_revertsOnZeroRegistry() public { vm.expectRevert(CredibleSafeGuard.ZeroCredibleRegistryAddress.selector); - new CredibleSafeGuard(ICredibleRegistry(address(0)), THRESHOLD); + new CredibleSafeGuard(ICredibleRegistry(address(0)), THRESHOLD, PROTOCOL_MANAGER); } function test_constructor_revertsOnZeroThreshold() public { vm.expectRevert(CredibleSafeGuard.ZeroFailOpenBlockThreshold.selector); - new CredibleSafeGuard(registry, 0); + new CredibleSafeGuard(registry, 0, PROTOCOL_MANAGER); + } + + function test_constructor_revertsOnZeroProtocolManager() public { + vm.expectRevert(InitialProtocolManager.ZeroInitialProtocolManager.selector); + new CredibleSafeGuard(registry, THRESHOLD, address(0)); + } + + // --------------------------------------------------------------------- + // IInitialProtocolManager: the guard declares its own Credible Layer manager + // --------------------------------------------------------------------- + + /// @dev The state oracle reads the manager through {IInitialProtocolManager}; confirm the guard + /// satisfies that interface's getter when called through the interface type. + function test_initialProtocolManager_conformsToInterface() public view { + IInitialProtocolManager asInterface = IInitialProtocolManager(address(guard)); + assertEq(asInterface.initialProtocolManager(), PROTOCOL_MANAGER); + } + + /// @dev The immutable is read from code, not storage; the value must be stable across calls. + function test_initialProtocolManager_isStableAcrossCalls() public view { + assertEq(guard.initialProtocolManager(), guard.initialProtocolManager()); + } + + /// @dev The guard must record whatever non-zero manager it was deployed with, independent of the + /// registry and threshold arguments. + function testFuzz_initialProtocolManager_storesConstructorValue( + address registry_, + uint256 threshold_, + address manager_ + ) public { + vm.assume(registry_ != address(0)); + vm.assume(threshold_ != 0); + vm.assume(manager_ != address(0)); + + CredibleSafeGuard fuzzGuard = new CredibleSafeGuard(ICredibleRegistry(registry_), threshold_, manager_); + + assertEq(fuzzGuard.initialProtocolManager(), manager_); + // The manager is stored independently of the other two immutables. + assertEq(address(fuzzGuard.credibleRegistry()), registry_); + assertEq(fuzzGuard.failOpenBlockThreshold(), threshold_); + } + + /// @dev Two guards deployed with different managers report their own value — the manager is + /// per-deployment, not shared state. + function test_initialProtocolManager_isPerDeployment() public { + CredibleSafeGuard otherGuard = new CredibleSafeGuard(registry, THRESHOLD, address(0xB0B)); + + assertEq(guard.initialProtocolManager(), PROTOCOL_MANAGER); + assertEq(otherGuard.initialProtocolManager(), address(0xB0B)); } // --------------------------------------------------------------------- @@ -311,7 +364,8 @@ contract CredibleSafeGuardTest is Test { } function test_failsOpen_whenRegistryHasNoCode() public { - CredibleSafeGuard codelessRegistryGuard = new CredibleSafeGuard(ICredibleRegistry(address(0xBEEF)), THRESHOLD); + CredibleSafeGuard codelessRegistryGuard = + new CredibleSafeGuard(ICredibleRegistry(address(0xBEEF)), THRESHOLD, PROTOCOL_MANAGER); assertTrue(codelessRegistryGuard.isCurrentBlockAllowed()); _checkGuard(codelessRegistryGuard); @@ -321,7 +375,7 @@ contract CredibleSafeGuardTest is Test { // isCredibleBlock burns >> 50k gas; the capped staticcall OOGs and fails open without // reverting the outer (Safe) transaction. GasGuzzlingRegistry guzzler = new GasGuzzlingRegistry(); - CredibleSafeGuard guzzlerGuard = new CredibleSafeGuard(guzzler, THRESHOLD); + CredibleSafeGuard guzzlerGuard = new CredibleSafeGuard(guzzler, THRESHOLD, PROTOCOL_MANAGER); assertTrue(guzzlerGuard.isCurrentBlockAllowed()); assertTrue(guzzlerGuard.failOpenActive()); @@ -333,7 +387,7 @@ contract CredibleSafeGuardTest is Test { // burns >> 50k gas. The capped staticcall on that read OOGs and fails open too. GasGuzzlingRegistry guzzler = new GasGuzzlingRegistry(); guzzler.setGuzzleIsCredible(false); - CredibleSafeGuard guzzlerGuard = new CredibleSafeGuard(guzzler, THRESHOLD); + CredibleSafeGuard guzzlerGuard = new CredibleSafeGuard(guzzler, THRESHOLD, PROTOCOL_MANAGER); assertTrue(guzzlerGuard.isCurrentBlockAllowed()); assertTrue(guzzlerGuard.failOpenActive()); @@ -345,7 +399,7 @@ contract CredibleSafeGuardTest is Test { // copies at most one word and rejects the over-long returndata, so the guard fails open // rather than trusting the payload or copying it unbounded. ReturnDataBombRegistry bomb = new ReturnDataBombRegistry(); - CredibleSafeGuard bombGuard = new CredibleSafeGuard(bomb, THRESHOLD); + CredibleSafeGuard bombGuard = new CredibleSafeGuard(bomb, THRESHOLD, PROTOCOL_MANAGER); assertTrue(bombGuard.isCurrentBlockAllowed()); assertTrue(bombGuard.failOpenActive());