diff --git a/foundry.toml b/foundry.toml index 39d02ebb..169101c6 100644 --- a/foundry.toml +++ b/foundry.toml @@ -54,13 +54,13 @@ forge-std = "1.16.1" "@openzeppelin-contracts-upgradeable" = "5.6.1" "rain-deploy" = "0.1.4" "rain-extrospection" = "0.1.1" -"rain-factory" = "0.1.1" +"rain-factory" = "0.1.5" "rain-math-fixedpoint" = "0.2.0" "rain-math-float" = "0.1.1" "rain-sol-codegen" = "0.1.0" "rain-solmem" = "0.1.3" "rain-tofu-erc20-decimals" = "0.1.1" -"rain-vats" = "0.1.6" +"rain-vats" = "0.1.7" "rain-verify-interface" = "0.1.0" [soldeer] diff --git a/remappings.txt b/remappings.txt index 51d38713..74505204 100644 --- a/remappings.txt +++ b/remappings.txt @@ -3,11 +3,11 @@ forge-std-1.16.1/=dependencies/forge-std-1.16.1/ rain-deploy-0.1.4/=dependencies/rain-deploy-0.1.4/ rain-extrospection-0.1.1/=dependencies/rain-extrospection-0.1.1/ -rain-factory-0.1.1/=dependencies/rain-factory-0.1.1/ +rain-factory-0.1.5/=dependencies/rain-factory-0.1.5/ rain-math-fixedpoint-0.2.0/=dependencies/rain-math-fixedpoint-0.2.0/ rain-math-float-0.1.1/=dependencies/rain-math-float-0.1.1/ rain-sol-codegen-0.1.0/=dependencies/rain-sol-codegen-0.1.0/ rain-solmem-0.1.3/=dependencies/rain-solmem-0.1.3/ rain-tofu-erc20-decimals-0.1.1/=dependencies/rain-tofu-erc20-decimals-0.1.1/ -rain-vats-0.1.6/=dependencies/rain-vats-0.1.6/ +rain-vats-0.1.7/=dependencies/rain-vats-0.1.7/ rain-verify-interface-0.1.0/=dependencies/rain-verify-interface-0.1.0/ diff --git a/script/20260619-deploy-v4-authoriser-clone.s.sol b/script/20260619-deploy-v4-authoriser-clone.s.sol index 78ce2b52..44397cfb 100644 --- a/script/20260619-deploy-v4-authoriser-clone.s.sol +++ b/script/20260619-deploy-v4-authoriser-clone.s.sol @@ -6,11 +6,11 @@ import {Script} from "forge-std-1.16.1/src/Script.sol"; import {console2} from "forge-std-1.16.1/src/console2.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; import {ERC1167_PREFIX, ERC1167_SUFFIX} from "rain-extrospection-0.1.1/src/lib/LibExtrospectERC1167Proxy.sol"; -import {ICloneableFactoryV2} from "rain-factory-0.1.1/src/interface/ICloneableFactoryV2.sol"; -import {LibCloneFactoryDeploy} from "rain-factory-0.1.1/src/lib/LibCloneFactoryDeploy.sol"; +import {ICloneableFactoryV2} from "rain-factory-0.1.5/src/interface/ICloneableFactoryV2.sol"; +import {LibNonceCloneFactory} from "../src/lib/LibNonceCloneFactory.sol"; import { OffchainAssetReceiptVaultAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; import {IGnosisSafe} from "../src/interface/IGnosisSafe.sol"; import {LibAuthoriserInvariants, RoleGrant} from "../src/lib/LibAuthoriserInvariants.sol"; @@ -28,7 +28,7 @@ error V4ImplNotDeployed(address impl); /// value in `LibProdDeployV4`. Impl has been replaced with different code. error V4ImplCodehashMismatch(address impl, bytes32 expected, bytes32 actual); -/// @notice The canonical `CloneFactory` from `rain-factory-0.1.1` is not +/// @notice The nonce-based `CloneFactory` pinned in `LibNonceCloneFactory` is not /// deployed at its pinned address. Zoltu deploy is missing on this network. error CloneFactoryNotDeployed(address factory); @@ -139,8 +139,7 @@ contract DeployV4AuthoriserClone is Script { uint256 internal constant AUTO_GRANTED_ADMIN_COUNT = 7; /// @notice The V4 authoriser clone pin for the active chain, selected by - /// `block.chainid` from `LibProdDeployV4` — `address(0)` until that chain's - /// clone is deployed and the pin hydrated. Reverts for any chain without a + /// `block.chainid` from `LibProdDeployV4`. Reverts for any chain without a /// pin rather than falling back to another chain's clone (reading the wrong /// chain's clone is the catastrophic failure this guard exists to prevent). /// @return The active chain's clone pin. @@ -186,7 +185,7 @@ contract DeployV4AuthoriserClone is Script { // Pre-flight: the canonical `CloneFactory` is deployed with the // pinned codehash. A missing/replaced factory would either // revert or hand back a clone under attacker-supplied bytecode. - address factoryAddr = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS; + address factoryAddr = LibNonceCloneFactory.CLONE_FACTORY_DEPLOYED_ADDRESS; assertCloneFactoryDeployed(factoryAddr); // Pre-flight: the clone pin is not already hydrated. If it is, @@ -270,7 +269,7 @@ contract DeployV4AuthoriserClone is Script { /// hold no `_ADMIN` role post-renounce. /// @param v4Impl The pinned V4 impl the clone proxies; the expected /// codehash is re-derived from this address so the check does not - /// depend on the (still-placeholder) codehash pin. + /// depend on the codehash pin. function _assertPostState(address clone, address deployer, address v4Impl) internal view { // EIP-1167 shape + embedded impl match what the pinned V4 impl // produces. @@ -342,7 +341,7 @@ contract DeployV4AuthoriserClone is Script { function assertCloneFactoryDeployed(address factory) internal view { if (factory.code.length == 0) revert CloneFactoryNotDeployed(factory); bytes32 actual = factory.codehash; - bytes32 expected = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH; + bytes32 expected = LibNonceCloneFactory.CLONE_FACTORY_DEPLOYED_CODEHASH; if (actual != expected) revert CloneFactoryCodehashMismatch(factory, expected, actual); } diff --git a/script/20260623-upgrade-receipt-vaults-to-v4.s.sol b/script/20260623-upgrade-receipt-vaults-to-v4.s.sol index 865838aa..7715b7ae 100644 --- a/script/20260623-upgrade-receipt-vaults-to-v4.s.sol +++ b/script/20260623-upgrade-receipt-vaults-to-v4.s.sol @@ -7,8 +7,8 @@ import {console2} from "forge-std-1.16.1/src/console2.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; import {IBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/IBeacon.sol"; -import {IAuthorizableV1} from "rain-vats-0.1.6/src/interface/IAuthorizableV1.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizableV1} from "rain-vats-0.1.7/src/interface/IAuthorizableV1.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {IGnosisSafe} from "../src/interface/IGnosisSafe.sol"; import {LibAuthoriserInvariants, RoleGrant} from "../src/lib/LibAuthoriserInvariants.sol"; import {LibProdDeployV1} from "../src/lib/LibProdDeployV1.sol"; @@ -33,9 +33,10 @@ error V4ImplementationNotDeployed(address implementation); /// @param actual The codehash observed on-chain. error V4CodehashMismatch(address implementation, bytes32 expected, bytes32 actual); -/// @notice The V4 authoriser clone constant in `LibAuthoriserInvariants` is still -/// the `address(0)` placeholder. The clone must be deployed (and its address -/// dropped into the lib) before the upgrade can be authored. +/// @notice The V4 authoriser clone pin +/// (`LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE`) is `address(0)` — no +/// deployed clone has been pinned for this chain, so the upgrade cannot be +/// authored. error V4AuthoriserCloneNotPinned(); /// @notice The V4 authoriser clone address is pinned but has no runtime code. @@ -101,11 +102,10 @@ error VaultAuthoriserMismatchPostUpgrade(address vault, address expected, addres /// - the V4 authoriser clone is pinned (non-zero), deployed, has the pinned /// EIP-1167 codehash, and holds every `LibAuthoriserInvariants.expectedGrants()` /// pair. -/// The V4 impl at `STOX_RECEIPT_VAULT_0_1_1` is deployed on Base with the -/// pinned codehash, so the impl-side pre-flight passes; the clone-pin check -/// fails red today because `STOX_PROD_AUTHORISER_V4_CLONE` is still -/// `address(0)` — the forcing function blocking the upgrade until the clone -/// is deployed and pinned. +/// The impl- and clone-side pre-flights both pass against live Base state: +/// the V4 impl at `STOX_RECEIPT_VAULT_0_1_1` is deployed with the pinned +/// codehash, and `STOX_PROD_AUTHORISER_V4_CLONE` names the deployed clone, +/// which carries the pinned EIP-1167 codehash and the full grant map. /// 2. **Build** — a multi-tx bundle: one `upgradeTo(V4 impl)` call per V1 /// production beacon (receipt, receipt vault, wrapped token vault), plus /// one `setAuthorizer(V4 clone)` call per production receipt vault @@ -178,7 +178,7 @@ contract UpgradeReceiptVaultsToV4 is Script { } /// @notice The V4 authoriser clone that every production receipt vault is - /// rewired onto. Placeholder until the clone is deployed. + /// rewired onto. Pinned in `LibProdDeployV4` from the deployed clone. address internal constant V4_AUTHORISER_CLONE = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE; /// @notice Human-readable name embedded in the emitted Tx Builder JSON's diff --git a/script/20260706-deploy-tokens-ethereum.s.sol b/script/20260706-deploy-tokens-ethereum.s.sol index 42f7c9d4..259436a9 100644 --- a/script/20260706-deploy-tokens-ethereum.s.sol +++ b/script/20260706-deploy-tokens-ethereum.s.sol @@ -7,10 +7,10 @@ import {console2} from "forge-std-1.16.1/src/console2.sol"; import {Vm} from "forge-std-1.16.1/src/Vm.sol"; import { OffchainAssetReceiptVaultConfigV2 -} from "rain-vats-0.1.6/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; -import {ReceiptVaultConfigV2} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; -import {IReceiptVaultV3} from "rain-vats-0.1.6/src/interface/IReceiptVaultV3.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +} from "rain-vats-0.1.7/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; +import {ReceiptVaultConfigV2} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; +import {IReceiptVaultV3} from "rain-vats-0.1.7/src/interface/IReceiptVaultV3.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {Ownable} from "@openzeppelin-contracts-5.6.1/access/Ownable.sol"; import {LibBeaconInvariants} from "../src/lib/LibBeaconInvariants.sol"; diff --git a/script/20260722-deploy-missing-tokens-ethereum.s.sol b/script/20260722-deploy-missing-tokens-ethereum.s.sol index dc02a09b..995d73e4 100644 --- a/script/20260722-deploy-missing-tokens-ethereum.s.sol +++ b/script/20260722-deploy-missing-tokens-ethereum.s.sol @@ -7,10 +7,10 @@ import {console2} from "forge-std-1.16.1/src/console2.sol"; import {Vm} from "forge-std-1.16.1/src/Vm.sol"; import { OffchainAssetReceiptVaultConfigV2 -} from "rain-vats-0.1.6/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; -import {ReceiptVaultConfigV2} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; -import {IReceiptVaultV3} from "rain-vats-0.1.6/src/interface/IReceiptVaultV3.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +} from "rain-vats-0.1.7/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; +import {ReceiptVaultConfigV2} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; +import {IReceiptVaultV3} from "rain-vats-0.1.7/src/interface/IReceiptVaultV3.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {Ownable} from "@openzeppelin-contracts-5.6.1/access/Ownable.sol"; import {LibBeaconInvariants} from "../src/lib/LibBeaconInvariants.sol"; import {IStoxUnifiedDeployerV1} from "../src/interface/IStoxUnifiedDeployerV1.sol"; diff --git a/script/20260722-swap-remaining-vault-authorisers.s.sol b/script/20260722-swap-remaining-vault-authorisers.s.sol index 456087b3..309ffd98 100644 --- a/script/20260722-swap-remaining-vault-authorisers.s.sol +++ b/script/20260722-swap-remaining-vault-authorisers.s.sol @@ -6,8 +6,8 @@ import {Script} from "forge-std-1.16.1/src/Script.sol"; import {console2} from "forge-std-1.16.1/src/console2.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; -import {IAuthorizableV1} from "rain-vats-0.1.6/src/interface/IAuthorizableV1.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizableV1} from "rain-vats-0.1.7/src/interface/IAuthorizableV1.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {IGnosisSafe} from "../src/interface/IGnosisSafe.sol"; import {LibAuthoriserInvariants, RoleGrant} from "../src/lib/LibAuthoriserInvariants.sol"; import {LibProdDeployV4} from "../src/generated/LibProdDeployV4.sol"; diff --git a/script/20260722-swap-rklb-authoriser.s.sol b/script/20260722-swap-rklb-authoriser.s.sol index 9393d26e..7250ac27 100644 --- a/script/20260722-swap-rklb-authoriser.s.sol +++ b/script/20260722-swap-rklb-authoriser.s.sol @@ -6,8 +6,8 @@ import {Script} from "forge-std-1.16.1/src/Script.sol"; import {console2} from "forge-std-1.16.1/src/console2.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; -import {IAuthorizableV1} from "rain-vats-0.1.6/src/interface/IAuthorizableV1.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizableV1} from "rain-vats-0.1.7/src/interface/IAuthorizableV1.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {IGnosisSafe} from "../src/interface/IGnosisSafe.sol"; import {LibAuthoriserInvariants, RoleGrant} from "../src/lib/LibAuthoriserInvariants.sol"; import {LibProdDeployV4} from "../src/generated/LibProdDeployV4.sol"; diff --git a/script/20260804-deploy-deterministic-v4-authoriser-clone.s.sol b/script/20260804-deploy-deterministic-v4-authoriser-clone.s.sol new file mode 100644 index 00000000..ac69b351 --- /dev/null +++ b/script/20260804-deploy-deterministic-v4-authoriser-clone.s.sol @@ -0,0 +1,203 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {Script} from "forge-std-1.16.1/src/Script.sol"; +import {console2} from "forge-std-1.16.1/src/console2.sol"; +import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; +import {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol"; +import {ICloneableFactoryV3} from "rain-factory-0.1.5/src/interface/ICloneableFactoryV3.sol"; +import {LibCloneFactoryDeploy} from "rain-factory-0.1.5/src/lib/LibCloneFactoryDeploy.sol"; +import { + OffchainAssetReceiptVaultAuthorizerV1Config +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; + +import {LibProdDeployV4} from "../src/generated/LibProdDeployV4.sol"; +import {LibAuthoriserInvariants} from "../src/lib/LibAuthoriserInvariants.sol"; +import {LibSafeInvariants} from "../src/lib/LibSafeInvariants.sol"; + +/// @notice The deterministic `CloneFactory` (rain-factory 0.1.5) has no code +/// at its pinned address on the active chain. It is Zoltu-deployed and +/// permissionless — deploy it (rain.factory `manual-sol-artifacts`) and rerun. +/// @param factory The pinned factory address with no code. +error DeterministicFactoryNotDeployed(address factory); + +/// @notice The deterministic `CloneFactory` runtime codehash does not match +/// the rain-factory 0.1.5 pin. The address holds something other than the +/// audited factory. +error DeterministicFactoryCodehashMismatch(address factory, bytes32 expected, bytes32 actual); + +/// @notice The V4 authoriser impl has no runtime code at its pin. The clone +/// would EIP-1167-proxy an empty account. +error DeterministicV4ImplNotDeployed(address impl); + +/// @notice The V4 authoriser impl's runtime codehash drifted from the pin. +error DeterministicV4ImplCodehashMismatch(address impl, bytes32 expected, bytes32 actual); + +/// @notice The factory's own prediction for `(impl, salt, deployer)` does not +/// land on the pinned migration target. The pin and the deploy inputs have +/// drifted apart; nothing may broadcast until they agree. +error PredictedCloneMismatch(address predicted, address pinned); + +/// @notice Code already exists at the migration target but its codehash is +/// not the pinned EIP-1167 shape. The target address is occupied by something +/// other than the expected clone — unrecoverable by rerunning. +error TargetOccupiedByForeignCode(address target, bytes32 expected, bytes32 actual); + +/// @notice The broadcasting key is not the pinned deterministic deployer. The +/// factory namespaces the CREATE2 salt by `msg.sender`, so broadcasting from +/// any other key would deploy to a different address than the pin. +error WrongDeterministicDeployer(address expected, address actual); + +/// @notice The freshly-deployed clone did not land on the pinned target. +error CloneNotAtTarget(address clone, address target); + +/// @notice The deployed clone's runtime codehash is not the pinned EIP-1167 +/// shape for the V4 impl. +error DeterministicCloneCodehashMismatch(address clone, bytes32 expected, bytes32 actual); + +/// @notice An `_ADMIN` role the authoriser's `initialize` auto-grants is +/// missing from the token-owner Safe on the fresh clone. +error SafeMissingAdminRole(bytes32 role, address safe); + +/// @title DeployDeterministicV4AuthoriserClone +/// @notice Broadcast script that deploys the V4 authoriser clone at its +/// deterministic migration target (#292) on the active chain: +/// +/// `LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC` +/// +/// The target is chain-invariant: the 0.1.5 `CloneFactory` and the V4 impl +/// are both Zoltu deploys (same address everywhere), and the salt/deployer +/// pair is pinned, so every chain's clone lands on the same address. The +/// script is idempotent per chain — it no-ops when the pinned clone already +/// occupies the target, so one workflow dispatch per chain converges. +/// +/// `initialAdmin` is the active chain's token-owner Safe: the authoriser's +/// `initialize` auto-grants the seven `_ADMIN` roles straight to the Safe, so +/// the deployer never holds any role on the clone and no renounce ceremony is +/// needed. The clone is otherwise inert — no operational grants, and no vault +/// references it — until the migration (grant mirroring + `setAuthorizer` + +/// live-pin flip) executes as its own scripted step. +contract DeployDeterministicV4AuthoriserClone is Script { + /// @notice Deploy the clone at the migration target, or no-op if it is + /// already there. Pre-flight asserts the Safe, factory, impl, and + /// prediction; post-state asserts landing address, EIP-1167 shape, and + /// the Safe's admin grants. + function run() external { + // Pre-flight: the active chain's token-owner Safe is intact (also + // gates the script to chains with prod config — anything else reverts + // inside the invariant lib). + address safe = LibSafeInvariants.assertActiveChainTokenOwnerSafe(block.chainid); + + // Pre-flight: the deterministic factory is deployed with the audited + // codehash. + address factory = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS; + if (factory.code.length == 0) revert DeterministicFactoryNotDeployed(factory); + if (factory.codehash != LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH) { + revert DeterministicFactoryCodehashMismatch( + factory, LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH, factory.codehash + ); + } + + // Pre-flight: the V4 impl is deployed with the audited codehash. + address impl = LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1; + if (impl.code.length == 0) revert DeterministicV4ImplNotDeployed(impl); + bytes32 implCodehash = LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_CODEHASH_0_1_1; + if (impl.codehash != implCodehash) { + revert DeterministicV4ImplCodehashMismatch(impl, implCodehash, impl.codehash); + } + + // Pre-flight: the factory's own prediction lands on the pin. + address target = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC; + address predicted = ICloneableFactoryV3(factory) + .predictDeterministicAddress( + impl, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER + ); + if (predicted != target) revert PredictedCloneMismatch(predicted, target); + + // Idempotency: the pinned clone already occupies the target — done, + // provided its initialized state also holds (the codehash cannot see + // initialization, and a same-address clone could have been initialized + // with different admin data). Any OTHER code at the target is + // unrecoverable by rerunning. + if (target.code.length != 0) { + if (target.codehash != LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH) { + revert TargetOccupiedByForeignCode( + target, LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH, target.codehash + ); + } + assertPostState(target, safe); + console2.log("Deterministic V4 authoriser clone already at target; nothing to do."); + console2.log("Target:", vm.toString(target)); + return; + } + + vm.startBroadcast(); + + // The factory namespaces the CREATE2 salt by `msg.sender`, so the + // pinned target only holds for the pinned deployer. + address deployer = msg.sender; + if (deployer != LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER) { + revert WrongDeterministicDeployer( + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER, deployer + ); + } + + address clone = ICloneableFactoryV3(factory) + .cloneDeterministic( + impl, + abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: safe})), + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT + ); + + vm.stopBroadcast(); + + if (clone != target) revert CloneNotAtTarget(clone, target); + assertPostState(clone, safe); + + console2.log("==== DETERMINISTIC V4 AUTHORISER CLONE DEPLOYED ===="); + console2.log("Clone:", vm.toString(clone)); + console2.log("CloneCodehash:", vm.toString(clone.codehash)); + console2.log("===================================================="); + } + + /// @notice Post-state assertion: the clone carries the pinned EIP-1167 + /// runtime shape and the Safe holds every auto-granted `_ADMIN` role. + /// Split from `run()` so tests can drive it against a clone they deployed + /// under `vm.prank`. + /// @param clone The freshly-deployed clone. + /// @param safe The `initialAdmin` the seven `_ADMIN` roles landed on. + function assertPostState(address clone, address safe) public view { + bytes32 expectedCodehash = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH; + if (clone.codehash != expectedCodehash) { + revert DeterministicCloneCodehashMismatch(clone, expectedCodehash, clone.codehash); + } + + bytes32[7] memory adminRoles = autoGrantedAdminRoles(); + for (uint256 i = 0; i < adminRoles.length; i++) { + if (!IAccessControl(clone).hasRole(adminRoles[i], safe)) { + revert SafeMissingAdminRole(adminRoles[i], safe); + } + } + + // The deploy key deploys and holds NOTHING: neither the hot deploy + // key nor the retired deploy EOA may appear anywhere in the clone's + // role graph, enforced at broadcast time. + LibAuthoriserInvariants.assertKeysHoldNoRoles(clone); + } + + /// @notice The seven `_ADMIN` roles the base + ST0x-override `initialize` + /// grant to the supplied `initialAdmin` config, in `_grantRole` order. + /// @return roles The seven role hashes. + function autoGrantedAdminRoles() public pure returns (bytes32[7] memory roles) { + roles[0] = keccak256("CERTIFY_ADMIN"); + roles[1] = keccak256("CONFISCATE_RECEIPT_ADMIN"); + roles[2] = keccak256("CONFISCATE_SHARES_ADMIN"); + roles[3] = keccak256("DEPOSIT_ADMIN"); + roles[4] = keccak256("WITHDRAW_ADMIN"); + roles[5] = keccak256("SCHEDULE_CORPORATE_ACTION_ADMIN"); + roles[6] = keccak256("CANCEL_CORPORATE_ACTION_ADMIN"); + } +} diff --git a/script/BuildPointers.sol b/script/BuildPointers.sol index c3642ef4..80b855b7 100644 --- a/script/BuildPointers.sol +++ b/script/BuildPointers.sol @@ -27,6 +27,12 @@ import { } from "../src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; import {ST0xOrchestrator} from "../src/concrete/ST0xOrchestrator.sol"; import {ST0xOrchestratorBeaconSetDeployer} from "../src/concrete/deploy/ST0xOrchestratorBeaconSetDeployer.sol"; +import {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol"; +import {LibCloneFactoryDeploy} from "rain-factory-0.1.5/src/lib/LibCloneFactoryDeploy.sol"; +import {ERC1167_PREFIX, ERC1167_SUFFIX} from "rain-extrospection-0.1.1/src/lib/LibExtrospectERC1167Proxy.sol"; +import { + DEPLOYED_ADDRESS as V4_AUTHORISER_IMPL_0_1_1 +} from "../src/generated/0_1_1/StoxOffchainAssetReceiptVaultAuthorizerV1.pointers.sol"; contract BuildPointers is Script { /// @notice The rolling "current source" snapshot tag — always `candidate`, @@ -148,6 +154,29 @@ contract BuildPointers is Script { string constant GEN_CURRENT_PATH = "src/generated/LibProdDeployCurrent.sol"; string constant GEN_OWNER = "0x8E4bdeec7CEB9570D440676345dA1dCe10329f5b"; + /// @notice Salt for the deterministic V4 authoriser clone migration target + /// (#292). The 0.1.5 `CloneFactory` namespaces it by the broadcasting + /// account, so the target address holds only when broadcast from + /// `V4_AUTHORISER_CLONE_DEPLOYER`. + bytes32 constant V4_AUTHORISER_CLONE_SALT = bytes32(0); + + /// @notice The account the deterministic clone deploy must broadcast from — + /// the EOA the CI deploy key resolves to: the creator of the live Base + /// clone `0x315b16fa…` via the nonce factory (Base tx + /// `0x26519d1c9090e6236cbd6e9c7f5d6eee7cf633da3a6653742914b3c17fe7d236`). + address constant V4_AUTHORISER_CLONE_DEPLOYER = 0xE8c6eDE25f0E7fAfE8fBc34770FaBa27d56c0E76; + + /// @notice Hex body of `data` without the `0x` prefix, for `hex"..."` + /// constant emission. + function hexBody(bytes memory data) internal pure returns (string memory) { + bytes memory prefixed = bytes(vm.toString(data)); + bytes memory body = new bytes(prefixed.length - 2); + for (uint256 i = 2; i < prefixed.length; i++) { + body[i - 2] = prefixed[i]; + } + return string(body); + } + // REUSE-IgnoreStart (the two SPDX lines below are the header EMITTED into // the generated files, not this script's own license — hide from reuse lint) string constant GEN_SPDX_LICENSE = "// SPDX-License-Identifier: LicenseRef-DCL-1.0"; @@ -358,6 +387,57 @@ contract BuildPointers is Script { "address constant STOX_PROD_AUTHORISER_V4_CLONE_ETHEREUM = address(0x66566cc91dEAf818859bD4b09B7903ac48998157);" ); vm.writeLine(GEN_V4_PATH, "uint256 constant V4_SWAP_DEADLINE = 1_793_491_200;"); + // Deterministic V4 authoriser clone MIGRATION TARGET (#292): the + // CREATE2 prediction against the 0.1.5 deterministic `CloneFactory`, + // identical on every chain (Zoltu factory + Zoltu impl + fixed + // deployer/salt). NOT the live clone — the live pins above stay + // authoritative until the migration re-points them. The runtime + // bytecode is embedded beside the codehash per #293. + bytes32 effSalt = keccak256(abi.encode(V4_AUTHORISER_CLONE_DEPLOYER, V4_AUTHORISER_CLONE_SALT)); + address deterministicClone = Clones.predictDeterministicAddress( + V4_AUTHORISER_IMPL_0_1_1, effSalt, LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS + ); + bytes memory deterministicCloneCode = abi.encodePacked(ERC1167_PREFIX, V4_AUTHORISER_IMPL_0_1_1, ERC1167_SUFFIX); + vm.writeLine( + GEN_V4_PATH, + string.concat( + "address constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC = address(", + vm.toString(deterministicClone), + ");" + ) + ); + vm.writeLine( + GEN_V4_PATH, + string.concat( + "bytes32 constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT = ", + vm.toString(V4_AUTHORISER_CLONE_SALT), + ";" + ) + ); + vm.writeLine( + GEN_V4_PATH, + string.concat( + "address constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER = address(", + vm.toString(V4_AUTHORISER_CLONE_DEPLOYER), + ");" + ) + ); + vm.writeLine( + GEN_V4_PATH, + string.concat( + "bytes constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODE = hex\"", + hexBody(deterministicCloneCode), + "\";" + ) + ); + vm.writeLine( + GEN_V4_PATH, + string.concat( + "bytes32 constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH = ", + vm.toString(keccak256(deterministicCloneCode)), + ";" + ) + ); for (uint256 t = 0; t < tags.length; t++) { for (uint256 c = 0; c < 12; c++) { if (pointerExists(tags[t], names[c])) { diff --git a/soldeer.lock b/soldeer.lock index 33997070..e5299706 100644 --- a/soldeer.lock +++ b/soldeer.lock @@ -35,10 +35,10 @@ integrity = "aa7366cac7d9c7610350a49b8126c667d83812a974539e79ccd38a5577cd626b" [[dependencies]] name = "rain-factory" -version = "0.1.1" -url = "https://soldeer-revisions.s3.amazonaws.com/rain-factory/0_1_1_20-05-2026_13:02:17_rain.zip" -checksum = "6b02e2b87983e196bc88cb8fb802a2720b252783429567864cd13336167c369d" -integrity = "be6a3343d0ea247d8b495b584cd21c3bf64f0a1396ca21aaa8ce31715df29842" +version = "0.1.5" +url = "https://soldeer-revisions.s3.amazonaws.com/rain-factory/0_1_5_12-07-2026_14:01:52_rain.zip" +checksum = "31f5ac857d16b8dad27eec11218a338ce13721114b0514b05e500b14edf8ce80" +integrity = "6e6eb9dc5174068e39d6ac80981210723088dde56c9cb0925ffe657ba3154cbc" [[dependencies]] name = "rain-math-fixedpoint" @@ -77,10 +77,10 @@ integrity = "9e1fccf893dd0d90aeb445c78f9eee3904bc50287ff1da30b954508f18412cd2" [[dependencies]] name = "rain-vats" -version = "0.1.6" -url = "https://soldeer-revisions.s3.amazonaws.com/rain-vats/0_1_6_01-06-2026_04:00:43_rain.zip" -checksum = "0072a9b57a18e59a82edaebc8e6c4e9271decc1156b08e96c57a1a6744dd8934" -integrity = "c2d9eeee64ad1274ee92623eeddfa4af7d0344df1976684154fc2b25794c23e3" +version = "0.1.7" +url = "https://soldeer-revisions.s3.amazonaws.com/rain-vats/0_1_7_12-07-2026_16:32:50_rain.zip" +checksum = "05a99d527c172e2231936248f868902063bf598bb376d22750e9db2cd7a1a678" +integrity = "c58bcd75b69558d32b9a11ac830707c9b4a427f6e518b80e3cec5e255765b6e7" [[dependencies]] name = "rain-verify-interface" diff --git a/src/concrete/ST0xOrchestrator.sol b/src/concrete/ST0xOrchestrator.sol index 4c60c473..b233135e 100644 --- a/src/concrete/ST0xOrchestrator.sol +++ b/src/concrete/ST0xOrchestrator.sol @@ -13,9 +13,9 @@ import {IERC1155Receiver} from "@openzeppelin-contracts-5.6.1/token/ERC1155/IERC import {IERC165} from "@openzeppelin-contracts-5.6.1/utils/introspection/IERC165.sol"; import {SignatureChecker} from "@openzeppelin-contracts-5.6.1/utils/cryptography/SignatureChecker.sol"; -import {OffchainAssetReceiptVault} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; -import {IReceiptV3} from "rain-vats-0.1.6/src/interface/IReceiptV3.sol"; -import {ReceiptVault} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; +import {OffchainAssetReceiptVault} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; +import {IReceiptV3} from "rain-vats-0.1.7/src/interface/IReceiptV3.sol"; +import {ReceiptVault} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; import {LibProdDeployCurrent} from "../generated/LibProdDeployCurrent.sol"; import {IMintRecipient} from "../interface/IMintRecipient.sol"; diff --git a/src/concrete/StoxCorporateActionsFacet.sol b/src/concrete/StoxCorporateActionsFacet.sol index 2d75d86c..7934366b 100644 --- a/src/concrete/StoxCorporateActionsFacet.sol +++ b/src/concrete/StoxCorporateActionsFacet.sol @@ -17,8 +17,8 @@ import { } from "../lib/LibCorporateActionNode.sol"; import {LibStockSplit} from "../lib/LibStockSplit.sol"; import {Float, LibDecimalFloat} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; -import {OffchainAssetReceiptVault} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; +import {OffchainAssetReceiptVault} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; /// @title StoxCorporateActionsFacet /// @notice Diamond facet implementing the corporate action linked list. diff --git a/src/concrete/StoxReceipt.sol b/src/concrete/StoxReceipt.sol index 447d48dd..50c030c7 100644 --- a/src/concrete/StoxReceipt.sol +++ b/src/concrete/StoxReceipt.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {Receipt} from "rain-vats-0.1.6/src/concrete/receipt/Receipt.sol"; +import {Receipt} from "rain-vats-0.1.7/src/concrete/receipt/Receipt.sol"; import {ERC1155Upgradeable} from "@openzeppelin-contracts-upgradeable-5.6.1/token/ERC1155/ERC1155Upgradeable.sol"; import {IERC1155} from "@openzeppelin-contracts-5.6.1/token/ERC1155/IERC1155.sol"; import {ICorporateActionsV1} from "../interface/ICorporateActionsV1.sol"; diff --git a/src/concrete/StoxReceiptVault.sol b/src/concrete/StoxReceiptVault.sol index 41229ecc..8f0eb590 100644 --- a/src/concrete/StoxReceiptVault.sol +++ b/src/concrete/StoxReceiptVault.sol @@ -2,8 +2,8 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {OffchainAssetReceiptVault} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {OffchainAssetReceiptVault} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; import {LibCorporateAction, SCHEDULE_CORPORATE_ACTION, CANCEL_CORPORATE_ACTION} from "../lib/LibCorporateAction.sol"; import {LibRebase} from "../lib/LibRebase.sol"; diff --git a/src/concrete/StoxWrappedTokenVault.sol b/src/concrete/StoxWrappedTokenVault.sol index d0697f23..7c6188e6 100644 --- a/src/concrete/StoxWrappedTokenVault.sol +++ b/src/concrete/StoxWrappedTokenVault.sol @@ -6,7 +6,7 @@ import { ERC4626Upgradeable } from "@openzeppelin-contracts-upgradeable-5.6.1/token/ERC20/extensions/ERC4626Upgradeable.sol"; import {ERC20Upgradeable} from "@openzeppelin-contracts-upgradeable-5.6.1/token/ERC20/ERC20Upgradeable.sol"; -import {ICLONEABLE_V2_SUCCESS, ICloneableV2} from "rain-factory-0.1.1/src/interface/ICloneableV2.sol"; +import {ICLONEABLE_V2_SUCCESS, ICloneableV2} from "rain-factory-0.1.5/src/interface/ICloneableV2.sol"; import {IERC20Metadata} from "@openzeppelin-contracts-5.6.1/token/ERC20/extensions/IERC20Metadata.sol"; /// @dev Error raised when a zero address is provided for the vault asset. diff --git a/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol b/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol index 73da1ec1..77197344 100644 --- a/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol +++ b/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol @@ -5,8 +5,8 @@ pragma solidity =0.8.25; import { OffchainAssetReceiptVaultAuthorizerV1, OffchainAssetReceiptVaultAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; -import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.1/src/interface/ICloneableV2.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.5/src/interface/ICloneableV2.sol"; import {SCHEDULE_CORPORATE_ACTION, CANCEL_CORPORATE_ACTION} from "../../lib/LibCorporateAction.sol"; /// @dev Role admin for SCHEDULE_CORPORATE_ACTION. diff --git a/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol b/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol index a04b84a6..068c8567 100644 --- a/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol +++ b/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.25; import { OffchainAssetReceiptVaultPaymentMintAuthorizerV1 -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; /// @title StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1 /// @notice An OffchainAssetReceiptVaultPaymentMintAuthorizerV1 specialized for diff --git a/src/concrete/deploy/StoxOffchainAssetReceiptVaultBeaconSetDeployer.sol b/src/concrete/deploy/StoxOffchainAssetReceiptVaultBeaconSetDeployer.sol index 4fc48079..f302427e 100644 --- a/src/concrete/deploy/StoxOffchainAssetReceiptVaultBeaconSetDeployer.sol +++ b/src/concrete/deploy/StoxOffchainAssetReceiptVaultBeaconSetDeployer.sol @@ -5,7 +5,7 @@ pragma solidity =0.8.25; import { OffchainAssetReceiptVaultBeaconSetDeployer, OffchainAssetReceiptVaultBeaconSetDeployerConfig -} from "rain-vats-0.1.6/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; +} from "rain-vats-0.1.7/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; import {LibProdDeployCurrent} from "../../generated/LibProdDeployCurrent.sol"; /// @title StoxOffchainAssetReceiptVaultBeaconSetDeployer diff --git a/src/concrete/deploy/StoxUnifiedDeployer.sol b/src/concrete/deploy/StoxUnifiedDeployer.sol index 33f8dc73..29838ce5 100644 --- a/src/concrete/deploy/StoxUnifiedDeployer.sol +++ b/src/concrete/deploy/StoxUnifiedDeployer.sol @@ -6,7 +6,7 @@ import { OffchainAssetReceiptVaultBeaconSetDeployer, OffchainAssetReceiptVaultConfigV2, OffchainAssetReceiptVault -} from "rain-vats-0.1.6/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; +} from "rain-vats-0.1.7/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; import {ERC165} from "@openzeppelin-contracts-5.6.1/utils/introspection/ERC165.sol"; import {StoxWrappedTokenVaultBeaconSetDeployer} from "./StoxWrappedTokenVaultBeaconSetDeployer.sol"; import {LibProdDeployCurrent} from "../../generated/LibProdDeployCurrent.sol"; diff --git a/src/concrete/deploy/StoxWrappedTokenVaultBeaconSetDeployer.sol b/src/concrete/deploy/StoxWrappedTokenVaultBeaconSetDeployer.sol index 5135d0ea..c447c8aa 100644 --- a/src/concrete/deploy/StoxWrappedTokenVaultBeaconSetDeployer.sol +++ b/src/concrete/deploy/StoxWrappedTokenVaultBeaconSetDeployer.sol @@ -5,7 +5,7 @@ pragma solidity =0.8.25; import {BeaconProxy} from "@openzeppelin-contracts-5.6.1/proxy/beacon/BeaconProxy.sol"; import {ERC165} from "@openzeppelin-contracts-5.6.1/utils/introspection/ERC165.sol"; import {StoxWrappedTokenVault} from "../StoxWrappedTokenVault.sol"; -import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.1/src/interface/ICloneableV2.sol"; +import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.5/src/interface/ICloneableV2.sol"; import {LibProdDeployCurrent} from "../../generated/LibProdDeployCurrent.sol"; import {IStoxWrappedTokenVaultBeaconSetDeployerV1} from "../../interface/IStoxWrappedTokenVaultBeaconSetDeployerV1.sol"; diff --git a/src/generated/LibProdDeployV4.sol b/src/generated/LibProdDeployV4.sol index ed09ecf4..dea79d28 100644 --- a/src/generated/LibProdDeployV4.sol +++ b/src/generated/LibProdDeployV4.sol @@ -143,6 +143,15 @@ library LibProdDeployV4 { 0x2089950d3cc1112dd66a58adcfadeadc490b50053ac67be8bc676b4a2dcd1717; address constant STOX_PROD_AUTHORISER_V4_CLONE_ETHEREUM = address(0x66566cc91dEAf818859bD4b09B7903ac48998157); uint256 constant V4_SWAP_DEADLINE = 1_793_491_200; + address constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC = address(0xE322d7D0d366cE0bddfACF6D25d11DdA1B13A86b); + bytes32 constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT = + 0x0000000000000000000000000000000000000000000000000000000000000000; + address constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER = + address(0xE8c6eDE25f0E7fAfE8fBc34770FaBa27d56c0E76); + bytes constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODE = + hex"363d3d373d3d3d363d732ea0d35d0b1f57c42e6130f298930228bcbfde9b5af43d82803e903d91602b57fd5bf3"; + bytes32 constant STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH = + 0x2089950d3cc1112dd66a58adcfadeadc490b50053ac67be8bc676b4a2dcd1717; address constant STOX_RECEIPT_0_1_1 = STOX_RECEIPT_ADDRESS_0_1_1_GEN; bytes32 constant STOX_RECEIPT_CODEHASH_0_1_1 = STOX_RECEIPT_CODEHASH_0_1_1_GEN; bytes constant STOX_RECEIPT_CREATION_CODE_0_1_1 = STOX_RECEIPT_CREATION_0_1_1_GEN; diff --git a/src/interface/IStoxUnifiedDeployerV1.sol b/src/interface/IStoxUnifiedDeployerV1.sol index 54e39cf3..76bbb147 100644 --- a/src/interface/IStoxUnifiedDeployerV1.sol +++ b/src/interface/IStoxUnifiedDeployerV1.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity ^0.8.25; -import {OffchainAssetReceiptVaultConfigV2} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; +import {OffchainAssetReceiptVaultConfigV2} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; /// @title IStoxUnifiedDeployerV1 /// @notice V1 interface for the StoxUnifiedDeployer. diff --git a/src/lib/LibAuthoriserInvariants.sol b/src/lib/LibAuthoriserInvariants.sol index 7201c861..461eb3ad 100644 --- a/src/lib/LibAuthoriserInvariants.sol +++ b/src/lib/LibAuthoriserInvariants.sol @@ -28,6 +28,15 @@ error ExpectedGrantMissing(address authoriser, bytes32 role, address grantee); /// @param holder The grantee found to hold `DEFAULT_ADMIN_ROLE`. error UnexpectedDefaultAdmin(address authoriser, address holder); +/// @notice A deploy key holds a role on the authoriser. The hot deploy key +/// deploys and holds NOTHING, and the retired deploy EOA must never reappear +/// in the role graph — a role on either is an escalation path outside the +/// pinned grant map. +/// @param authoriser The authoriser carrying the unexpected grant. +/// @param role The role id found on the key. +/// @param holder The key holding it. +error DeployKeyHoldsRole(address authoriser, bytes32 role, address holder); + /// @notice The authoriser's runtime codehash does not match the pinned /// EIP-1167 minimal-proxy codehash, i.e. the clone does not proxy the /// audited implementation. @@ -180,6 +189,42 @@ library LibAuthoriserInvariants { revert ExpectedGrantMissing(authoriser, grants[i].role, grants[i].grantee); } } + assertKeysHoldNoRoles(authoriser); + } + + /// @notice Assert neither the hot deploy key (the pinned deterministic + /// deployer) nor the retired deploy EOA (`BEACON_INITIAL_OWNER`) holds + /// ANY role in the known role universe on the supplied authoriser — the + /// 13 pinned role ids from `expectedGrants` plus `DEFAULT_ADMIN_ROLE`. + /// The hot key deploys and holds nothing; the retired key must never + /// reappear in the role graph. Reverts `DeployKeyHoldsRole` naming the + /// exact role and key on violation. + /// @dev Role IDS are chain-invariant (only grantee SLOTS vary per chain), + /// so the universe is built from the Base map. Both key pins come from + /// the generated `LibProdDeployV4` — no address literals here. Composed + /// into `assertExpectedGrants(address,address)`, so every per-chain prod + /// fork assertion enforces it; also called directly by the deterministic + /// clone deploy's post-state. + /// @param authoriser The authoriser to validate. + function assertKeysHoldNoRoles(address authoriser) internal view { + IAccessControl acl = IAccessControl(authoriser); + address hotKey = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER; + address retiredKey = LibProdDeployV4.BEACON_INITIAL_OWNER; + RoleGrant[] memory grants = expectedGrants(GRANTEE_TOKEN_OWNER_SAFE); + for (uint256 i = 0; i < grants.length; i++) { + if (acl.hasRole(grants[i].role, hotKey)) { + revert DeployKeyHoldsRole(authoriser, grants[i].role, hotKey); + } + if (acl.hasRole(grants[i].role, retiredKey)) { + revert DeployKeyHoldsRole(authoriser, grants[i].role, retiredKey); + } + } + if (acl.hasRole(DEFAULT_ADMIN_ROLE, hotKey)) { + revert DeployKeyHoldsRole(authoriser, DEFAULT_ADMIN_ROLE, hotKey); + } + if (acl.hasRole(DEFAULT_ADMIN_ROLE, retiredKey)) { + revert DeployKeyHoldsRole(authoriser, DEFAULT_ADMIN_ROLE, retiredKey); + } } /// @notice Full authoriser-side invariant bundle against the current diff --git a/src/lib/LibNonceCloneFactory.sol b/src/lib/LibNonceCloneFactory.sol new file mode 100644 index 00000000..005df230 --- /dev/null +++ b/src/lib/LibNonceCloneFactory.sol @@ -0,0 +1,30 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity ^0.8.25; + +/// @title LibNonceCloneFactory +/// @notice Pins for the nonce-based `CloneFactory` (rain-factory 0.1.1) that +/// every live V4 authoriser clone was deployed through. rain-factory 0.1.5's +/// `LibCloneFactoryDeploy` pins the deterministic-only factory, which exposes +/// no `clone(address,bytes)` — the clone-deploy script must keep targeting the +/// nonce-based factory these pins describe. +library LibNonceCloneFactory { + /// The address of the nonce-based `CloneFactory` when deployed with the + /// rain standard zoltu deployer. + address constant CLONE_FACTORY_DEPLOYED_ADDRESS = address(0x444acC29d63fa643E8adCC35FD9aa6DE111dCb39); + + /// The code hash of the nonce-based `CloneFactory` when deployed with the + /// rain standard zoltu deployer. This can be used to verify that the + /// deployed contract has the expected bytecode, which provides stronger + /// guarantees than just checking the address. + bytes32 constant CLONE_FACTORY_DEPLOYED_CODEHASH = + bytes32(0xf21b813c7075a1621285df3a8369d0652c31ea80cb807be1aaadafeecd134475); + + /// The full runtime bytecode of the nonce-based `CloneFactory` as deployed, + /// read from the live Base deploy. The codehash pin alone cannot recreate + /// historical behavior once compiler/optimizer drift makes the source + /// unable to reproduce it; the embedded code can. + /// `CLONE_FACTORY_DEPLOYED_CODEHASH == keccak256(CLONE_FACTORY_DEPLOYED_CODE)`. + bytes constant CLONE_FACTORY_DEPLOYED_CODE = + hex"608060405234801561000f575f80fd5b5060043610610029575f3560e01c80630fbe133c1461002d575b5f80fd5b61004061003b3660046102fb565b610069565b60405173ffffffffffffffffffffffffffffffffffffffff909116815260200160405180910390f35b5f8373ffffffffffffffffffffffffffffffffffffffff163b5f036100ba576040517ff432283200000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f6100c485610213565b6040805133815273ffffffffffffffffffffffffffffffffffffffff888116602083015283168183015290519192507f274b5f356634f32a865af65bdc3d8205939d9413d75e1f367652e4f3b24d0c3a919081900360600190a16040517f439fab910000000000000000000000000000000000000000000000000000000081527fe0e57eda3f08f2a93bbe980d3df7f9c315eac41181f58b865a13d917fe769fc39073ffffffffffffffffffffffffffffffffffffffff83169063439fab91906101949088908890600401610391565b6020604051808303815f875af11580156101b0573d5f803e3d5ffd5b505050506040513d601f19601f820116820180604052508101906101d491906103dd565b1461020b576040517f19b991a800000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b949350505050565b5f61021e825f610224565b92915050565b5f8147101561026c576040517fcf4791810000000000000000000000000000000000000000000000000000000081524760048201526024810183905260440160405180910390fd5b763d602d80600a3d3981f3363d3d373d3d3d363d730000008360601b60e81c175f526e5af43d82803e903d91602b57fd5bf38360781b176020526037600983f0905073ffffffffffffffffffffffffffffffffffffffff811661021e576040517fb06ebf3d00000000000000000000000000000000000000000000000000000000815260040160405180910390fd5b5f805f6040848603121561030d575f80fd5b833573ffffffffffffffffffffffffffffffffffffffff81168114610330575f80fd5b9250602084013567ffffffffffffffff8082111561034c575f80fd5b818601915086601f83011261035f575f80fd5b81358181111561036d575f80fd5b87602082850101111561037e575f80fd5b6020830194508093505050509250925092565b60208152816020820152818360408301375f818301604090810191909152601f9092017fffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffe0160101919050565b5f602082840312156103ed575f80fd5b505191905056"; +} diff --git a/test/script/20260619-deploy-v4-authoriser-clone.t.sol b/test/script/20260619-deploy-v4-authoriser-clone.t.sol index ec12dfae..9a04e71d 100644 --- a/test/script/20260619-deploy-v4-authoriser-clone.t.sol +++ b/test/script/20260619-deploy-v4-authoriser-clone.t.sol @@ -6,13 +6,13 @@ import {Test} from "forge-std-1.16.1/src/Test.sol"; import {VmSafe} from "forge-std-1.16.1/src/Vm.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol"; -import {LibCloneFactoryDeploy} from "rain-factory-0.1.1/src/lib/LibCloneFactoryDeploy.sol"; +import {LibNonceCloneFactory} from "../../src/lib/LibNonceCloneFactory.sol"; import {ERC1167_PREFIX, ERC1167_SUFFIX} from "rain-extrospection-0.1.1/src/lib/LibExtrospectERC1167Proxy.sol"; -import {ICloneableFactoryV2} from "rain-factory-0.1.1/src/interface/ICloneableFactoryV2.sol"; +import {ICloneableFactoryV2} from "rain-factory-0.1.5/src/interface/ICloneableFactoryV2.sol"; import { OffchainAssetReceiptVaultAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; import { DeployV4AuthoriserClone, @@ -63,7 +63,7 @@ contract DeployV4AuthoriserCloneTest is Test { vm.deal(deployer, 100 ether); safe = LibSafeInvariants.STOX_TOKEN_OWNER_SAFE; v4Impl = LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1; - cloneFactory = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS; + cloneFactory = LibNonceCloneFactory.CLONE_FACTORY_DEPLOYED_ADDRESS; StoxOffchainAssetReceiptVaultAuthorizerV1 impl = new StoxOffchainAssetReceiptVaultAuthorizerV1(); v4ImplRuntime = address(impl).code; @@ -180,7 +180,7 @@ contract DeployV4AuthoriserCloneTest is Test { selectBaseFork(); bytes memory bogusCode = hex"60016000526001601ff3"; vm.etch(cloneFactory, bogusCode); - bytes32 expected = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH; + bytes32 expected = LibNonceCloneFactory.CLONE_FACTORY_DEPLOYED_CODEHASH; bytes32 actual = keccak256(bogusCode); vm.expectRevert(abi.encodeWithSelector(CloneFactoryCodehashMismatch.selector, cloneFactory, expected, actual)); vm.prank(deployer, deployer); @@ -346,4 +346,15 @@ contract DeployV4AuthoriserCloneTest is Test { // The live invariant map satisfies the script's own slice guard. harness.callAssertGrantsSliceInvariant(); } + + /// @notice The nonce-factory codehash pin is the keccak of the embedded + /// runtime bytecode — the same shape the prod pins assert, so the hash + /// can never drift from the code that reproduces it. + function testNonceCloneFactoryCodehashMatchesEmbeddedCode() external pure { + assertEq( + keccak256(LibNonceCloneFactory.CLONE_FACTORY_DEPLOYED_CODE), + LibNonceCloneFactory.CLONE_FACTORY_DEPLOYED_CODEHASH, + "CLONE_FACTORY_DEPLOYED_CODEHASH is not keccak256(CLONE_FACTORY_DEPLOYED_CODE)" + ); + } } diff --git a/test/script/20260623-upgrade-receipt-vaults-to-v4.t.sol b/test/script/20260623-upgrade-receipt-vaults-to-v4.t.sol index e0baa774..b9b0b8b2 100644 --- a/test/script/20260623-upgrade-receipt-vaults-to-v4.t.sol +++ b/test/script/20260623-upgrade-receipt-vaults-to-v4.t.sol @@ -6,7 +6,7 @@ import {Test} from "forge-std-1.16.1/src/Test.sol"; import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol"; import {Ownable} from "@openzeppelin-contracts-5.6.1/access/Ownable.sol"; -import {IAuthorizableV1} from "rain-vats-0.1.6/src/interface/IAuthorizableV1.sol"; +import {IAuthorizableV1} from "rain-vats-0.1.7/src/interface/IAuthorizableV1.sol"; import {LibBeaconInvariants} from "../../src/lib/LibBeaconInvariants.sol"; import {LibTokenInvariants} from "../../src/lib/LibTokenInvariants.sol"; diff --git a/test/script/20260722-swap-remaining-vault-authorisers.t.sol b/test/script/20260722-swap-remaining-vault-authorisers.t.sol index deb84c6f..fef43237 100644 --- a/test/script/20260722-swap-remaining-vault-authorisers.t.sol +++ b/test/script/20260722-swap-remaining-vault-authorisers.t.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.25; import {Test} from "forge-std-1.16.1/src/Test.sol"; import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol"; -import {IAuthorizableV1} from "rain-vats-0.1.6/src/interface/IAuthorizableV1.sol"; +import {IAuthorizableV1} from "rain-vats-0.1.7/src/interface/IAuthorizableV1.sol"; import { SwapRemainingVaultAuthorisers, diff --git a/test/script/20260722-swap-rklb-authoriser.t.sol b/test/script/20260722-swap-rklb-authoriser.t.sol index 381b1a4c..a6dfe9bd 100644 --- a/test/script/20260722-swap-rklb-authoriser.t.sol +++ b/test/script/20260722-swap-rklb-authoriser.t.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.25; import {Test} from "forge-std-1.16.1/src/Test.sol"; import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol"; -import {IAuthorizableV1} from "rain-vats-0.1.6/src/interface/IAuthorizableV1.sol"; +import {IAuthorizableV1} from "rain-vats-0.1.7/src/interface/IAuthorizableV1.sol"; import { SwapRklbAuthoriser, diff --git a/test/script/20260804-deploy-deterministic-v4-authoriser-clone.t.sol b/test/script/20260804-deploy-deterministic-v4-authoriser-clone.t.sol new file mode 100644 index 00000000..7e4c7552 --- /dev/null +++ b/test/script/20260804-deploy-deterministic-v4-authoriser-clone.t.sol @@ -0,0 +1,287 @@ +// SPDX-License-Identifier: LicenseRef-DCL-1.0 +// SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd +pragma solidity =0.8.25; + +import {Test} from "forge-std-1.16.1/src/Test.sol"; +import {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol"; +import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol"; +import {ICloneableFactoryV3} from "rain-factory-0.1.5/src/interface/ICloneableFactoryV3.sol"; +import {LibCloneFactoryDeploy} from "rain-factory-0.1.5/src/lib/LibCloneFactoryDeploy.sol"; +import {ERC1167_PREFIX, ERC1167_SUFFIX} from "rain-extrospection-0.1.1/src/lib/LibExtrospectERC1167Proxy.sol"; +import { + OffchainAssetReceiptVaultAuthorizerV1Config +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; + +import { + DeployDeterministicV4AuthoriserClone, + DeterministicFactoryNotDeployed, + DeterministicFactoryCodehashMismatch, + DeterministicV4ImplCodehashMismatch, + SafeMissingAdminRole, + TargetOccupiedByForeignCode +} from "../../script/20260804-deploy-deterministic-v4-authoriser-clone.s.sol"; +import {LibProdDeployV4} from "../../src/generated/LibProdDeployV4.sol"; +import {LibSafeInvariants} from "../../src/lib/LibSafeInvariants.sol"; +import {LibStoxDeployNetworks} from "../../src/lib/LibStoxDeployNetworks.sol"; + +/// @title DeployDeterministicV4AuthoriserCloneTest +/// @notice Offline pin-consistency checks plus Base-fork drives of the +/// 20260804 deterministic clone deploy. The migration target has no live code +/// yet — EXPECTED until the script broadcasts — so live-state tests assert +/// the pinned shape only when code is present. +contract DeployDeterministicV4AuthoriserCloneTest is Test { + DeployDeterministicV4AuthoriserClone internal script; + + function selectBaseFork() internal { + vm.createSelectFork(LibRainDeploy.BASE); + script = new DeployDeterministicV4AuthoriserClone(); + } + + /// @notice The pinned target re-derives offline from (impl, salt, + /// deployer, factory) with the factory's deployer-namespaced CREATE2 + /// salt. No fork required — pure CREATE2 math. + function testTargetPinRederivesOffline() external pure { + bytes32 effSalt = keccak256( + abi.encode( + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT + ) + ); + address predicted = Clones.predictDeterministicAddress( + LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1, + effSalt, + LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS + ); + assertEq( + predicted, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC, + "pinned deterministic target does not re-derive from its inputs" + ); + } + + /// @notice The pinned runtime bytecode re-derives as the EIP-1167 shape + /// around the pinned V4 impl. + function testTargetPinCodeRederives() external pure { + assertEq( + abi.encodePacked( + ERC1167_PREFIX, LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1, ERC1167_SUFFIX + ), + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODE, + "pinned clone bytecode is not the EIP-1167 shape around the pinned impl" + ); + } + + /// @notice The pinned deployer is the EOA that created the live Base + /// clone via the nonce factory (Base tx + /// `0x26519d1c9090e6236cbd6e9c7f5d6eee7cf633da3a6653742914b3c17fe7d236`) + /// — the account the CI deploy key resolves to — and NOT the retired + /// `0x8E4bdeec…` EOA that `BEACON_INITIAL_OWNER` records. + function testDeployerPinIsTheLiveCloneCreator() external pure { + assertEq( + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER, + 0xE8c6eDE25f0E7fAfE8fBc34770FaBa27d56c0E76, + "deterministic deployer pin drifted from the live clone's creation-tx sender" + ); + assertTrue( + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER + != LibProdDeployV4.BEACON_INITIAL_OWNER, + "deterministic deployer pin must not be the retired beacon-owner EOA" + ); + } + + /// @notice The pinned codehash is the keccak of the pinned bytecode. + function testTargetPinCodehashMatchesCode() external pure { + assertEq( + keccak256(LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODE), + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH, + "pinned codehash is not keccak256 of the pinned bytecode" + ); + } + + /// @notice On the live Base fork, the factory's own prediction lands on + /// the pin, and the target either has no code yet (EXPECTED pre-deploy) + /// or already carries the pinned clone. + function testBaseLiveFactoryPredictsPin() external { + selectBaseFork(); + address factory = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS; + assertGt(factory.code.length, 0, "deterministic factory missing on Base"); + assertEq( + factory.codehash, LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH, "factory codehash drift on Base" + ); + address predicted = ICloneableFactoryV3(factory) + .predictDeterministicAddress( + LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER + ); + assertEq(predicted, LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC, "live prediction != pin"); + + address target = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC; + if (target.code.length != 0) { + assertEq( + target.codehash, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH, + "target occupied by foreign code" + ); + } + } + + /// @notice Against the REAL factory on the Base fork: a deterministic + /// clone deployed from the pinned deployer/salt lands exactly on the pin + /// and the script's post-state assertions hold. Once the broadcast has + /// happened for real, the live clone at the target must satisfy the same + /// post-state instead — the property is asserted either way. + function testCloneFromPinnedDeployerLandsOnPin() external { + selectBaseFork(); + address target = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC; + address safe = LibSafeInvariants.safeForChainId(block.chainid); + + if (target.code.length == 0) { + address deployer = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER; + vm.deal(deployer, 1 ether); + vm.prank(deployer, deployer); + address clone = ICloneableFactoryV3(LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS) + .cloneDeterministic( + LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1, + abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: safe})), + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT + ); + assertEq(clone, target, "clone did not land on the pinned target"); + script.assertPostState(clone, safe); + } else { + script.assertPostState(target, safe); + } + } + + /// @notice `run()` no-ops (no revert, no broadcast) when a properly + /// initialized pinned clone already occupies the target — a real factory + /// deploy from the pinned deployer, not an etch, so the idempotent path + /// sees genuine initialized state. + function testRunNoOpsWhenTargetAlreadyHydrated() external { + selectBaseFork(); + address target = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC; + if (target.code.length == 0) { + address deployer = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER; + address safe = LibSafeInvariants.safeForChainId(block.chainid); + vm.deal(deployer, 1 ether); + vm.prank(deployer, deployer); + ICloneableFactoryV3(LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS) + .cloneDeterministic( + LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1, + abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: safe})), + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT + ); + } + script.run(); + } + + /// @notice `run()` rejects a shape-matching clone at the target whose + /// initialized state is missing the Safe's admin grants — the codehash + /// alone is not proof of correct initialization. + /// + /// Only testable while the target is empty: `vm.etch` replaces code but + /// keeps storage, so once the broadcast has hydrated the target for real + /// the etched clone inherits the live grants and `run()` correctly + /// no-ops — the uninitialized shape this test fabricates no longer + /// exists at this address. The hydrated target's post-state is asserted + /// by `testRunNoOpsWhenTargetAlreadyHydrated` and + /// `testCloneFromPinnedDeployerLandsOnPin` instead. + function testRunRejectsUninitializedCloneAtTarget() external { + selectBaseFork(); + address target = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC; + if (target.code.length != 0) { + return; + } + vm.etch(target, LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODE); + vm.expectRevert( + abi.encodeWithSelector( + SafeMissingAdminRole.selector, + script.autoGrantedAdminRoles()[0], + LibSafeInvariants.safeForChainId(LibSafeInvariants.BASE_CHAIN_ID) + ) + ); + script.run(); + } + + /// @notice `run()` rejects a target occupied by anything other than the + /// pinned clone shape. + function testRunRejectsForeignCodeAtTarget() external { + selectBaseFork(); + address target = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC; + vm.etch(target, hex"fe"); + vm.expectRevert( + abi.encodeWithSelector( + TargetOccupiedByForeignCode.selector, + target, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_CODEHASH, + target.codehash + ) + ); + script.run(); + } + + /// @notice `run()` rejects a missing deterministic factory. + function testRunRejectsMissingFactory() external { + selectBaseFork(); + address factory = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS; + vm.etch(factory, ""); + vm.expectRevert(abi.encodeWithSelector(DeterministicFactoryNotDeployed.selector, factory)); + script.run(); + } + + /// @notice `run()` rejects a factory whose codehash drifts from the + /// rain-factory 0.1.5 pin. + function testRunRejectsFactoryCodehashDrift() external { + selectBaseFork(); + address factory = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS; + vm.etch(factory, hex"fe"); + vm.expectRevert( + abi.encodeWithSelector( + DeterministicFactoryCodehashMismatch.selector, + factory, + LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_CODEHASH, + factory.codehash + ) + ); + script.run(); + } + + /// @notice `run()` rejects a V4 impl whose codehash drifts from the pin. + function testRunRejectsImplCodehashDrift() external { + selectBaseFork(); + address impl = LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1; + vm.etch(impl, hex"fe"); + vm.expectRevert( + abi.encodeWithSelector( + DeterministicV4ImplCodehashMismatch.selector, + impl, + LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_CODEHASH_0_1_1, + impl.codehash + ) + ); + script.run(); + } + + /// @notice On the Ethereum fork the same pin holds: prediction matches + /// when the factory is present; while it is not yet Zoltu-deployed there, + /// `run()` fail-safes with the typed factory-missing error. + function testEthereumFactoryGate() external { + vm.createSelectFork(LibStoxDeployNetworks.ETHEREUM); + script = new DeployDeterministicV4AuthoriserClone(); + address factory = LibCloneFactoryDeploy.CLONE_FACTORY_DEPLOYED_ADDRESS; + if (factory.code.length == 0) { + vm.expectRevert(abi.encodeWithSelector(DeterministicFactoryNotDeployed.selector, factory)); + script.run(); + } else { + address predicted = ICloneableFactoryV3(factory) + .predictDeterministicAddress( + LibProdDeployV4.STOX_OFFCHAIN_ASSET_RECEIPT_VAULT_AUTHORIZER_V1_0_1_1, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_SALT, + LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER + ); + assertEq( + predicted, LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC, "Ethereum prediction != pin" + ); + } + } +} diff --git a/test/src/concrete/DelegatecallHarness.sol b/test/src/concrete/DelegatecallHarness.sol index f0b547ac..5adf82da 100644 --- a/test/src/concrete/DelegatecallHarness.sol +++ b/test/src/concrete/DelegatecallHarness.sol @@ -3,7 +3,7 @@ pragma solidity =0.8.25; import {LibCorporateAction} from "../../../src/lib/LibCorporateAction.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; /// @dev Minimal harness that delegates calls to a facet. Also exposes an /// `authorizer()` function so the facet's `OffchainAssetReceiptVault(address diff --git a/test/src/concrete/MockAuthorizer.sol b/test/src/concrete/MockAuthorizer.sol index 43f94504..4308f268 100644 --- a/test/src/concrete/MockAuthorizer.sol +++ b/test/src/concrete/MockAuthorizer.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; /// @dev Mock authorizer used by the facet tests. Records the most recent /// `authorize` call so tests can assert the per-action context that the facet diff --git a/test/src/concrete/MockVault.sol b/test/src/concrete/MockVault.sol index ea9e63a1..33db57b3 100644 --- a/test/src/concrete/MockVault.sol +++ b/test/src/concrete/MockVault.sol @@ -3,7 +3,7 @@ pragma solidity =0.8.25; import {MockCorporateActionsReadBase} from "../lib/MockCorporateActionsReadBase.sol"; -import {IReceiptManagerV2} from "rain-vats-0.1.6/src/interface/IReceiptManagerV2.sol"; +import {IReceiptManagerV2} from "rain-vats-0.1.7/src/interface/IReceiptManagerV2.sol"; /// @dev Mock vault combining `ICorporateActionsV1` (corporate-action read /// surface, inherited from `MockCorporateActionsReadBase`) and diff --git a/test/src/concrete/PermissiveAuthorizer.sol b/test/src/concrete/PermissiveAuthorizer.sol index 99ce320b..8161c255 100644 --- a/test/src/concrete/PermissiveAuthorizer.sol +++ b/test/src/concrete/PermissiveAuthorizer.sol @@ -2,7 +2,7 @@ // SPDX-FileCopyrightText: Copyright (c) 2020 Rain Open Source Software Ltd pragma solidity =0.8.25; -import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; /// @dev Permissive authorizer used by the fallback routing tests. Records the /// most recent call and allows every permission by default so we can exercise diff --git a/test/src/concrete/ST0xOrchestrator.t.sol b/test/src/concrete/ST0xOrchestrator.t.sol index 73159d66..441e1279 100644 --- a/test/src/concrete/ST0xOrchestrator.t.sol +++ b/test/src/concrete/ST0xOrchestrator.t.sol @@ -25,9 +25,9 @@ import {ReentrantMintRecipient} from "./ReentrantMintRecipient.sol"; import {ReentrantBurnVault} from "./ReentrantBurnVault.sol"; import {MockManagerRevert1155} from "./MockManagerRevert1155.sol"; import {ReentrancyGuardTransient} from "@openzeppelin-contracts-5.6.1/utils/ReentrancyGuardTransient.sol"; -import {OffchainAssetReceiptVault} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; -import {ReceiptVault} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; -import {IReceiptV3} from "rain-vats-0.1.6/src/interface/IReceiptV3.sol"; +import {OffchainAssetReceiptVault} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; +import {ReceiptVault} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; +import {IReceiptV3} from "rain-vats-0.1.7/src/interface/IReceiptV3.sol"; /// @dev Comprehensive unit + fuzz tests for the SINGLETON `ST0xOrchestrator`. /// All external dependencies (vault, receipt, ERC-20 shares, the production diff --git a/test/src/concrete/StoxCorporateActionsFacet.authorizerIntegration.t.sol b/test/src/concrete/StoxCorporateActionsFacet.authorizerIntegration.t.sol index ddcc292e..002be582 100644 --- a/test/src/concrete/StoxCorporateActionsFacet.authorizerIntegration.t.sol +++ b/test/src/concrete/StoxCorporateActionsFacet.authorizerIntegration.t.sol @@ -8,15 +8,15 @@ import { OffchainAssetReceiptVaultAuthorizerV1Config, CERTIFY, CERTIFY_ADMIN -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; import { StoxOffchainAssetReceiptVaultAuthorizerV1, SCHEDULE_CORPORATE_ACTION_ADMIN, CANCEL_CORPORATE_ACTION_ADMIN } from "../../../src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol"; -import {Unauthorized} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {Unauthorized} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; -import {CloneFactory} from "rain-factory-0.1.1/src/concrete/CloneFactory.sol"; +import {CloneFactory} from "rain-factory-0.1.5/src/concrete/CloneFactory.sol"; /// @title StoxCorporateActionsFacetAuthorizerIntegrationTest /// @notice Tests that the real OffchainAssetReceiptVaultAuthorizerV1 handles @@ -31,7 +31,11 @@ contract StoxCorporateActionsFacetAuthorizerIntegrationTest is Test { StoxOffchainAssetReceiptVaultAuthorizerV1 impl = new StoxOffchainAssetReceiptVaultAuthorizerV1(); CloneFactory factory = new CloneFactory(); return StoxOffchainAssetReceiptVaultAuthorizerV1( - factory.clone(address(impl), abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: ADMIN}))) + factory.cloneDeterministic( + address(impl), + abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: ADMIN})), + bytes32(0) + ) ); } diff --git a/test/src/concrete/StoxCorporateActionsFacet.t.sol b/test/src/concrete/StoxCorporateActionsFacet.t.sol index f8e70ef5..1434821d 100644 --- a/test/src/concrete/StoxCorporateActionsFacet.t.sol +++ b/test/src/concrete/StoxCorporateActionsFacet.t.sol @@ -25,7 +25,7 @@ import { ActionDoesNotExist, InvalidMask } from "../../../src/error/ErrCorporateAction.sol"; -import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {Float, LibDecimalFloat} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; import { CorporateActionNode, diff --git a/test/src/concrete/StoxReceiptRebaseIntegrationTest.t.sol b/test/src/concrete/StoxReceiptRebaseIntegrationTest.t.sol index 629716b8..3acb2e23 100644 --- a/test/src/concrete/StoxReceiptRebaseIntegrationTest.t.sol +++ b/test/src/concrete/StoxReceiptRebaseIntegrationTest.t.sol @@ -11,7 +11,7 @@ import { LibCorporateActionReceipt, CORPORATE_ACTION_RECEIPT_STORAGE_LOCATION } from "../../../src/lib/LibCorporateActionReceipt.sol"; -import {IReceiptManagerV2} from "rain-vats-0.1.6/src/interface/IReceiptManagerV2.sol"; +import {IReceiptManagerV2} from "rain-vats-0.1.7/src/interface/IReceiptManagerV2.sol"; import {IERC1155Errors} from "@openzeppelin-contracts-5.6.1/interfaces/draft-IERC6093.sol"; import {MockVault} from "./MockVault.sol"; import {TestStoxReceipt} from "./TestStoxReceipt.sol"; diff --git a/test/src/concrete/StoxReceiptVault.setAuthorizerGuard.t.sol b/test/src/concrete/StoxReceiptVault.setAuthorizerGuard.t.sol index 8f794a45..59c3590b 100644 --- a/test/src/concrete/StoxReceiptVault.setAuthorizerGuard.t.sol +++ b/test/src/concrete/StoxReceiptVault.setAuthorizerGuard.t.sol @@ -13,19 +13,19 @@ import { } from "../../../src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; import { OffchainAssetReceiptVaultAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; import { OffchainAssetReceiptVaultPaymentMintAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; -import {CloneFactory} from "rain-factory-0.1.1/src/concrete/CloneFactory.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; +import {CloneFactory} from "rain-factory-0.1.5/src/concrete/CloneFactory.sol"; import {VerifyAlwaysApproved} from "rain-verify-interface-0.1.0/src/concrete/VerifyAlwaysApproved.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; import {IERC165} from "@openzeppelin-contracts-5.6.1/utils/introspection/IERC165.sol"; import { IncompatibleAuthorizer, OffchainAssetReceiptVault -} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; +} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; import {AuthorizerMissingCorporateActionAdmin} from "../../../src/error/ErrCorporateAction.sol"; import {SCHEDULE_CORPORATE_ACTION, CANCEL_CORPORATE_ACTION} from "../../../src/lib/LibCorporateAction.sol"; import {MockERC20} from "../../concrete/MockERC20.sol"; @@ -52,14 +52,17 @@ contract StoxReceiptVaultSetAuthorizerGuardTest is Test { maxSharesSupply: 1e27 }) ); - return StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1(factory.clone(address(impl), initData)); + return StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1( + factory.cloneDeterministic(address(impl), initData, bytes32(0)) + ); } function _newCorporateActionsAuthorizer() internal returns (StoxOffchainAssetReceiptVaultAuthorizerV1) { StoxOffchainAssetReceiptVaultAuthorizerV1 impl = new StoxOffchainAssetReceiptVaultAuthorizerV1(); CloneFactory factory = new CloneFactory(); bytes memory initData = abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: OWNER})); - return StoxOffchainAssetReceiptVaultAuthorizerV1(factory.clone(address(impl), initData)); + return + StoxOffchainAssetReceiptVaultAuthorizerV1(factory.cloneDeterministic(address(impl), initData, bytes32(0))); } /// Pairing the PaymentMint authorizer (missing corporate-action role diff --git a/test/src/concrete/StoxReceiptVaultFallbackRouting.t.sol b/test/src/concrete/StoxReceiptVaultFallbackRouting.t.sol index d64ac635..be613039 100644 --- a/test/src/concrete/StoxReceiptVaultFallbackRouting.t.sol +++ b/test/src/concrete/StoxReceiptVaultFallbackRouting.t.sol @@ -13,7 +13,7 @@ import { import {LibProdDeployV4} from "../../../src/generated/LibProdDeployV4.sol"; import {STOCK_SPLIT_V1_TYPE_HASH, UnknownActionType} from "../../../src/lib/LibCorporateAction.sol"; import {CompletionFilter, NODE_NONE} from "../../../src/lib/LibCorporateActionNode.sol"; -import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {IAuthorizeV1, Unauthorized} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {Float, LibDecimalFloat} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; import {LibTestTofu} from "../../lib/LibTestTofu.sol"; import {PermissiveAuthorizer} from "./PermissiveAuthorizer.sol"; diff --git a/test/src/concrete/StoxWrappedTokenVault.t.sol b/test/src/concrete/StoxWrappedTokenVault.t.sol index 2b0b893f..ae96d63a 100644 --- a/test/src/concrete/StoxWrappedTokenVault.t.sol +++ b/test/src/concrete/StoxWrappedTokenVault.t.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.25; import {Test} from "forge-std-1.16.1/src/Test.sol"; import {StoxWrappedTokenVault, ZeroAsset} from "../../../src/concrete/StoxWrappedTokenVault.sol"; -import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.1/src/interface/ICloneableV2.sol"; +import {ICloneableV2, ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.5/src/interface/ICloneableV2.sol"; import {BeaconProxy} from "@openzeppelin-contracts-5.6.1/proxy/beacon/BeaconProxy.sol"; import { StoxWrappedTokenVaultBeaconSetDeployer, diff --git a/test/src/concrete/StoxWrappedTokenVaultV1.prod.base.t.sol b/test/src/concrete/StoxWrappedTokenVaultV1.prod.base.t.sol index bfcc8b91..4a1e5a1f 100644 --- a/test/src/concrete/StoxWrappedTokenVaultV1.prod.base.t.sol +++ b/test/src/concrete/StoxWrappedTokenVaultV1.prod.base.t.sol @@ -7,7 +7,7 @@ import {LibProdDeployV1} from "../../../src/lib/LibProdDeployV1.sol"; import {LibTestProd} from "../../lib/LibTestProd.sol"; import {IBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/IBeacon.sol"; import {BeaconProxy} from "@openzeppelin-contracts-5.6.1/proxy/beacon/BeaconProxy.sol"; -import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.1/src/interface/ICloneableV2.sol"; +import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.5/src/interface/ICloneableV2.sol"; /// @title StoxWrappedTokenVaultV1ProdBaseTest /// @notice Fork tests demonstrating V1 on-chain behaviour that differs from diff --git a/test/src/concrete/authorize/FailingSuperInitAuthorizer.sol b/test/src/concrete/authorize/FailingSuperInitAuthorizer.sol index 9c9d03ed..4e38376e 100644 --- a/test/src/concrete/authorize/FailingSuperInitAuthorizer.sol +++ b/test/src/concrete/authorize/FailingSuperInitAuthorizer.sol @@ -4,7 +4,7 @@ pragma solidity =0.8.25; import { OffchainAssetReceiptVaultAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; import { StoxOffchainAssetReceiptVaultAuthorizerV1 } from "../../../../src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol"; diff --git a/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.initializeGuard.t.sol b/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.initializeGuard.t.sol index d5fe91d4..88f5566a 100644 --- a/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.initializeGuard.t.sol +++ b/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.initializeGuard.t.sol @@ -7,13 +7,13 @@ import {Clones} from "@openzeppelin-contracts-5.6.1/proxy/Clones.sol"; import {SCHEDULE_CORPORATE_ACTION, CANCEL_CORPORATE_ACTION} from "../../../../src/lib/LibCorporateAction.sol"; import { OffchainAssetReceiptVaultAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; import { SCHEDULE_CORPORATE_ACTION_ADMIN, CANCEL_CORPORATE_ACTION_ADMIN } from "../../../../src/concrete/authorize/StoxOffchainAssetReceiptVaultAuthorizerV1.sol"; import {FailingSuperInitAuthorizer} from "./FailingSuperInitAuthorizer.sol"; -import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.1/src/interface/ICloneableV2.sol"; +import {ICLONEABLE_V2_SUCCESS} from "rain-factory-0.1.5/src/interface/ICloneableV2.sol"; contract StoxOffchainAssetReceiptVaultAuthorizerV1InitializeGuardTest is Test { address constant ADMIN = address(uint160(uint256(keccak256("ADMIN")))); diff --git a/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.corporateActionPairingGap.t.sol b/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.corporateActionPairingGap.t.sol index 667c474c..a2a442e3 100644 --- a/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.corporateActionPairingGap.t.sol +++ b/test/src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.corporateActionPairingGap.t.sol @@ -8,8 +8,8 @@ import { } from "../../../../src/concrete/authorize/StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; import { OffchainAssetReceiptVaultPaymentMintAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; -import {CloneFactory} from "rain-factory-0.1.1/src/concrete/CloneFactory.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultPaymentMintAuthorizerV1.sol"; +import {CloneFactory} from "rain-factory-0.1.5/src/concrete/CloneFactory.sol"; import {VerifyAlwaysApproved} from "rain-verify-interface-0.1.0/src/concrete/VerifyAlwaysApproved.sol"; import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessControl.sol"; import {SCHEDULE_CORPORATE_ACTION, CANCEL_CORPORATE_ACTION} from "../../../../src/lib/LibCorporateAction.sol"; @@ -44,7 +44,9 @@ contract StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1CorporateActionPair maxSharesSupply: 1e27 }) ); - return StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1(factory.clone(address(impl), initData)); + return StoxOffchainAssetReceiptVaultPaymentMintAuthorizerV1( + factory.cloneDeterministic(address(impl), initData, bytes32(0)) + ); } /// SCHEDULE_CORPORATE_ACTION's role admin falls back to diff --git a/test/src/concrete/deploy/StoxProdV2.t.sol b/test/src/concrete/deploy/StoxProdV2.t.sol index aff4534c..7bdebebe 100644 --- a/test/src/concrete/deploy/StoxProdV2.t.sol +++ b/test/src/concrete/deploy/StoxProdV2.t.sol @@ -13,7 +13,7 @@ import {IBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/IBeacon.sol"; import {Ownable} from "@openzeppelin-contracts-5.6.1/access/Ownable.sol"; import { IOffchainAssetReceiptVaultBeaconSetDeployerV1 -} from "rain-vats-0.1.6/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV1.sol"; +} from "rain-vats-0.1.7/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV1.sol"; /// @title StoxProdV2Test /// @notice Fork tests verifying all V2 Zoltu deployments exist on all diff --git a/test/src/concrete/deploy/StoxProdV4.t.sol b/test/src/concrete/deploy/StoxProdV4.t.sol index f3a1fc4d..ba90d47f 100644 --- a/test/src/concrete/deploy/StoxProdV4.t.sol +++ b/test/src/concrete/deploy/StoxProdV4.t.sol @@ -10,7 +10,7 @@ import {LibBeaconInvariants} from "../../../../src/lib/LibBeaconInvariants.sol"; import {IBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/IBeacon.sol"; import { IOffchainAssetReceiptVaultBeaconSetDeployerV2 -} from "rain-vats-0.1.6/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV2.sol"; +} from "rain-vats-0.1.7/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV2.sol"; /// @title StoxProdV4Test /// @notice Fork test verifying every V4 Zoltu deployment exists on-chain at its diff --git a/test/src/concrete/deploy/StoxUnifiedDeployer.newTokenAndWrapperVault.t.sol b/test/src/concrete/deploy/StoxUnifiedDeployer.newTokenAndWrapperVault.t.sol index ec7fcad8..139dc185 100644 --- a/test/src/concrete/deploy/StoxUnifiedDeployer.newTokenAndWrapperVault.t.sol +++ b/test/src/concrete/deploy/StoxUnifiedDeployer.newTokenAndWrapperVault.t.sol @@ -5,8 +5,8 @@ pragma solidity =0.8.25; import {Test, Vm} from "forge-std-1.16.1/src/Test.sol"; import { OffchainAssetReceiptVaultConfigV2 -} from "rain-vats-0.1.6/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; -import {ReceiptVaultConfigV2} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; +} from "rain-vats-0.1.7/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; +import {ReceiptVaultConfigV2} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; import {StoxUnifiedDeployer} from "../../../../src/concrete/deploy/StoxUnifiedDeployer.sol"; import {StoxWrappedTokenVault} from "../../../../src/concrete/StoxWrappedTokenVault.sol"; import {LibProdDeployV4} from "../../../../src/generated/LibProdDeployV4.sol"; diff --git a/test/src/concrete/deploy/StoxUnifiedDeployer.prod.base.t.sol b/test/src/concrete/deploy/StoxUnifiedDeployer.prod.base.t.sol index 3bc3e551..41e4ce5c 100644 --- a/test/src/concrete/deploy/StoxUnifiedDeployer.prod.base.t.sol +++ b/test/src/concrete/deploy/StoxUnifiedDeployer.prod.base.t.sol @@ -10,9 +10,9 @@ import {LibTestProd} from "../../../lib/LibTestProd.sol"; import {LibTestDeploy} from "../../../lib/LibTestDeploy.sol"; import { IOffchainAssetReceiptVaultBeaconSetDeployerV1 -} from "rain-vats-0.1.6/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV1.sol"; -import {OffchainAssetReceiptVaultConfigV2} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; -import {ReceiptVaultConfigV2} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; +} from "rain-vats-0.1.7/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV1.sol"; +import {OffchainAssetReceiptVaultConfigV2} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; +import {ReceiptVaultConfigV2} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; import { StoxWrappedTokenVaultBeaconSetDeployer } from "../../../../src/concrete/deploy/StoxWrappedTokenVaultBeaconSetDeployer.sol"; diff --git a/test/src/concrete/deploy/StoxUnifiedDeployer.t.sol b/test/src/concrete/deploy/StoxUnifiedDeployer.t.sol index 5ca83067..f9166223 100644 --- a/test/src/concrete/deploy/StoxUnifiedDeployer.t.sol +++ b/test/src/concrete/deploy/StoxUnifiedDeployer.t.sol @@ -7,7 +7,7 @@ import { OffchainAssetReceiptVaultBeaconSetDeployer, OffchainAssetReceiptVaultConfigV2, OffchainAssetReceiptVault -} from "rain-vats-0.1.6/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; +} from "rain-vats-0.1.7/src/concrete/deploy/OffchainAssetReceiptVaultBeaconSetDeployer.sol"; import {StoxUnifiedDeployer} from "../../../../src/concrete/deploy/StoxUnifiedDeployer.sol"; import {LibProdDeployV4} from "../../../../src/generated/LibProdDeployV4.sol"; import {StoxWrappedTokenVault} from "../../../../src/concrete/StoxWrappedTokenVault.sol"; @@ -15,7 +15,7 @@ import { StoxWrappedTokenVaultBeaconSetDeployer } from "../../../../src/concrete/deploy/StoxWrappedTokenVaultBeaconSetDeployer.sol"; import {LibTestDeploy} from "../../../lib/LibTestDeploy.sol"; -import {ReceiptVaultConfigV2} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; +import {ReceiptVaultConfigV2} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; import {MockERC20} from "../../../concrete/MockERC20.sol"; contract StoxUnifiedDeployerTest is Test { diff --git a/test/src/concrete/integration/BurnInfoAuditTrail.t.sol b/test/src/concrete/integration/BurnInfoAuditTrail.t.sol index e1dc25b7..2b429025 100644 --- a/test/src/concrete/integration/BurnInfoAuditTrail.t.sol +++ b/test/src/concrete/integration/BurnInfoAuditTrail.t.sol @@ -3,8 +3,8 @@ pragma solidity =0.8.25; import {IERC20} from "@openzeppelin-contracts-5.6.1/token/ERC20/IERC20.sol"; -import {IReceiptVaultV1} from "rain-vats-0.1.6/src/interface/deprecated/IReceiptVaultV1.sol"; -import {IReceiptV3} from "rain-vats-0.1.6/src/interface/IReceiptV3.sol"; +import {IReceiptVaultV1} from "rain-vats-0.1.7/src/interface/deprecated/IReceiptVaultV1.sol"; +import {IReceiptV3} from "rain-vats-0.1.7/src/interface/IReceiptV3.sol"; import {IST0xOrchestratorV1, MintAuthV1} from "../../../../src/interface/IST0xOrchestratorV1.sol"; import {OrchestratorIntegrationTest} from "./OrchestratorIntegrationTest.sol"; diff --git a/test/src/concrete/integration/CertificationLapse.t.sol b/test/src/concrete/integration/CertificationLapse.t.sol index f55c98a5..4e3e6882 100644 --- a/test/src/concrete/integration/CertificationLapse.t.sol +++ b/test/src/concrete/integration/CertificationLapse.t.sol @@ -3,7 +3,7 @@ pragma solidity =0.8.25; import {IERC20} from "@openzeppelin-contracts-5.6.1/token/ERC20/IERC20.sol"; -import {CertificationExpired} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +import {CertificationExpired} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; import {MintAuthV1} from "../../../../src/interface/IST0xOrchestratorV1.sol"; import {OrchestratorIntegrationTest} from "./OrchestratorIntegrationTest.sol"; diff --git a/test/src/concrete/integration/MintWithEcdsaSignature.t.sol b/test/src/concrete/integration/MintWithEcdsaSignature.t.sol index ef8253bf..5e2a6e22 100644 --- a/test/src/concrete/integration/MintWithEcdsaSignature.t.sol +++ b/test/src/concrete/integration/MintWithEcdsaSignature.t.sol @@ -3,7 +3,7 @@ pragma solidity =0.8.25; import {IERC20} from "@openzeppelin-contracts-5.6.1/token/ERC20/IERC20.sol"; -import {Unauthorized} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +import {Unauthorized} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {ST0xOrchestrator} from "../../../../src/concrete/ST0xOrchestrator.sol"; import {IST0xOrchestratorV1, MintAuthV1, Digest} from "../../../../src/interface/IST0xOrchestratorV1.sol"; diff --git a/test/src/concrete/integration/OrchestratorIntegrationTest.sol b/test/src/concrete/integration/OrchestratorIntegrationTest.sol index d7e3faae..6921d9bb 100644 --- a/test/src/concrete/integration/OrchestratorIntegrationTest.sol +++ b/test/src/concrete/integration/OrchestratorIntegrationTest.sol @@ -7,7 +7,7 @@ import {Test} from "forge-std-1.16.1/src/Test.sol"; import {IERC1155} from "@openzeppelin-contracts-5.6.1/token/ERC1155/IERC1155.sol"; import {UpgradeableBeacon} from "@openzeppelin-contracts-5.6.1/proxy/beacon/UpgradeableBeacon.sol"; import {BeaconProxy} from "@openzeppelin-contracts-5.6.1/proxy/beacon/BeaconProxy.sol"; -import {CloneFactory} from "rain-factory-0.1.1/src/concrete/CloneFactory.sol"; +import {CloneFactory} from "rain-factory-0.1.5/src/concrete/CloneFactory.sol"; import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol"; import {Float} from "rain-math-float-0.1.1/src/lib/LibDecimalFloat.sol"; import { @@ -15,12 +15,12 @@ import { DEPOSIT, WITHDRAW, CERTIFY -} from "rain-vats-0.1.6/src/concrete/vault/OffchainAssetReceiptVault.sol"; +} from "rain-vats-0.1.7/src/concrete/vault/OffchainAssetReceiptVault.sol"; import { OffchainAssetReceiptVaultAuthorizerV1Config -} from "rain-vats-0.1.6/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; -import {ReceiptVaultConfigV2} from "rain-vats-0.1.6/src/abstract/ReceiptVault.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; +} from "rain-vats-0.1.7/src/concrete/authorize/OffchainAssetReceiptVaultAuthorizerV1.sol"; +import {ReceiptVaultConfigV2} from "rain-vats-0.1.7/src/abstract/ReceiptVault.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; import {ST0xOrchestrator} from "../../../../src/concrete/ST0xOrchestrator.sol"; import {IMintRecipient} from "../../../../src/interface/IMintRecipient.sol"; @@ -148,8 +148,10 @@ abstract contract OrchestratorIntegrationTest is Test { StoxOffchainAssetReceiptVaultAuthorizerV1 authorizerImpl = new StoxOffchainAssetReceiptVaultAuthorizerV1(); CloneFactory factory = new CloneFactory(); return StoxOffchainAssetReceiptVaultAuthorizerV1( - factory.clone( - address(authorizerImpl), abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: ADMIN})) + factory.cloneDeterministic( + address(authorizerImpl), + abi.encode(OffchainAssetReceiptVaultAuthorizerV1Config({initialAdmin: ADMIN})), + bytes32(0) ) ); } diff --git a/test/src/concrete/upgrade/V3UpgradeShadowFork.t.sol b/test/src/concrete/upgrade/V3UpgradeShadowFork.t.sol index d58c07f7..e41239ef 100644 --- a/test/src/concrete/upgrade/V3UpgradeShadowFork.t.sol +++ b/test/src/concrete/upgrade/V3UpgradeShadowFork.t.sol @@ -20,11 +20,11 @@ import { } from "../../../../src/interface/ICorporateActionsV1.sol"; import {CompletionFilter} from "../../../../src/lib/LibCorporateActionNode.sol"; import {LibRainDeploy} from "rain-deploy-0.1.4/src/lib/LibRainDeploy.sol"; -import {IReceiptVaultV3} from "rain-vats-0.1.6/src/interface/IReceiptVaultV3.sol"; -import {IReceiptV3} from "rain-vats-0.1.6/src/interface/IReceiptV3.sol"; -import {IAuthorizableV1} from "rain-vats-0.1.6/src/interface/IAuthorizableV1.sol"; -import {IAuthorizeV1} from "rain-vats-0.1.6/src/interface/IAuthorizeV1.sol"; -import {ICertifiableV1} from "rain-vats-0.1.6/src/interface/ICertifiableV1.sol"; +import {IReceiptVaultV3} from "rain-vats-0.1.7/src/interface/IReceiptVaultV3.sol"; +import {IReceiptV3} from "rain-vats-0.1.7/src/interface/IReceiptV3.sol"; +import {IAuthorizableV1} from "rain-vats-0.1.7/src/interface/IAuthorizableV1.sol"; +import {IAuthorizeV1} from "rain-vats-0.1.7/src/interface/IAuthorizeV1.sol"; +import {ICertifiableV1} from "rain-vats-0.1.7/src/interface/ICertifiableV1.sol"; import {ERC1967_BEACON_SLOT} from "rain-extrospection-0.1.1/src/lib/LibExtrospectERC1967BeaconProxy.sol"; /// @title V3UpgradeShadowForkTest diff --git a/test/src/lib/LibAuthoriserInvariants.t.sol b/test/src/lib/LibAuthoriserInvariants.t.sol index e0288be8..56a78141 100644 --- a/test/src/lib/LibAuthoriserInvariants.t.sol +++ b/test/src/lib/LibAuthoriserInvariants.t.sol @@ -7,7 +7,8 @@ import {IAccessControl} from "@openzeppelin-contracts-5.6.1/access/IAccessContro import { LibAuthoriserInvariants, UnexpectedDefaultAdmin, - AuthoriserImplCodehashMismatch + AuthoriserImplCodehashMismatch, + DeployKeyHoldsRole } from "../../../src/lib/LibAuthoriserInvariants.sol"; import {LibProdDeployV4} from "../../../src/generated/LibProdDeployV4.sol"; import {LibAuthoriserInvariantsHarness} from "./LibAuthoriserInvariantsHarness.sol"; @@ -79,4 +80,39 @@ contract LibAuthoriserInvariantsTest is Test { ); harness.callAssertExpectedGrants(clone); } + + /// @notice Neither the hot deploy key nor the retired deploy EOA holds + /// any role on the live production clone. Passes against live Base + /// state; also runs implicitly inside every `assertExpectedGrants`. + function testAssertKeysHoldNoRolesPassesLive() external { + selectBaseFork(); + LibAuthoriserInvariants.assertKeysHoldNoRoles(LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE); + } + + /// @notice `assertKeysHoldNoRoles` reverts `DeployKeyHoldsRole` when the + /// hot deploy key holds a role from the pinned universe. + function testAssertKeysHoldNoRolesRejectsHotKeyRole() external { + selectBaseFork(); + address clone = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE; + address hotKey = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE_DETERMINISTIC_DEPLOYER; + bytes32 role = keccak256("DEPOSIT_ADMIN"); + vm.mockCall(clone, abi.encodeWithSelector(IAccessControl.hasRole.selector, role, hotKey), abi.encode(true)); + LibAuthoriserInvariantsHarness harness = new LibAuthoriserInvariantsHarness(); + vm.expectRevert(abi.encodeWithSelector(DeployKeyHoldsRole.selector, clone, role, hotKey)); + harness.callAssertKeysHoldNoRoles(clone); + } + + /// @notice `assertKeysHoldNoRoles` reverts `DeployKeyHoldsRole` when the + /// retired deploy EOA holds `DEFAULT_ADMIN_ROLE`. + function testAssertKeysHoldNoRolesRejectsRetiredKeyDefaultAdmin() external { + selectBaseFork(); + address clone = LibProdDeployV4.STOX_PROD_AUTHORISER_V4_CLONE; + address retiredKey = LibProdDeployV4.BEACON_INITIAL_OWNER; + vm.mockCall( + clone, abi.encodeWithSelector(IAccessControl.hasRole.selector, bytes32(0), retiredKey), abi.encode(true) + ); + LibAuthoriserInvariantsHarness harness = new LibAuthoriserInvariantsHarness(); + vm.expectRevert(abi.encodeWithSelector(DeployKeyHoldsRole.selector, clone, bytes32(0), retiredKey)); + harness.callAssertKeysHoldNoRoles(clone); + } } diff --git a/test/src/lib/LibAuthoriserInvariantsHarness.sol b/test/src/lib/LibAuthoriserInvariantsHarness.sol index 01e7bba0..03eb73be 100644 --- a/test/src/lib/LibAuthoriserInvariantsHarness.sol +++ b/test/src/lib/LibAuthoriserInvariantsHarness.sol @@ -16,4 +16,8 @@ contract LibAuthoriserInvariantsHarness { function callAssertExpectedGrants(address authoriser) external view { LibAuthoriserInvariants.assertExpectedGrants(authoriser); } + + function callAssertKeysHoldNoRoles(address authoriser) external view { + LibAuthoriserInvariants.assertKeysHoldNoRoles(authoriser); + } } diff --git a/test/src/lib/LibTokenInvariants.addresses.t.sol b/test/src/lib/LibTokenInvariants.addresses.t.sol index b558ddf1..1183ec14 100644 --- a/test/src/lib/LibTokenInvariants.addresses.t.sol +++ b/test/src/lib/LibTokenInvariants.addresses.t.sol @@ -8,12 +8,12 @@ import {LibProdDeployV1} from "../../../src/lib/LibProdDeployV1.sol"; import {LibTestProd} from "../../lib/LibTestProd.sol"; import {IERC20Metadata} from "@openzeppelin-contracts-5.6.1/token/ERC20/extensions/IERC20Metadata.sol"; import {IERC4626} from "@openzeppelin-contracts-5.6.1/interfaces/IERC4626.sol"; -import {IReceiptVaultV3} from "rain-vats-0.1.6/src/interface/IReceiptVaultV3.sol"; -import {IReceiptV3} from "rain-vats-0.1.6/src/interface/IReceiptV3.sol"; +import {IReceiptVaultV3} from "rain-vats-0.1.7/src/interface/IReceiptVaultV3.sol"; +import {IReceiptV3} from "rain-vats-0.1.7/src/interface/IReceiptV3.sol"; import { IOffchainAssetReceiptVaultBeaconSetDeployerV1 -} from "rain-vats-0.1.6/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV1.sol"; -import {ICertifiableV1} from "rain-vats-0.1.6/src/interface/ICertifiableV1.sol"; +} from "rain-vats-0.1.7/src/interface/IOffchainAssetReceiptVaultBeaconSetDeployerV1.sol"; +import {ICertifiableV1} from "rain-vats-0.1.7/src/interface/ICertifiableV1.sol"; import { ERC1967_BEACON_SLOT, LibExtrospectERC1967BeaconProxy