Files
proxmox/docs/runbooks/MULTI_CHAIN_EXECUTION_DETERMINISTIC_DEPLOYMENT.md
defiQUG bea1903ac9
Some checks failed
Deploy to Phoenix / deploy (push) Has been cancelled
Sync all local changes: docs, config, scripts, submodule refs, verification evidence
Co-authored-by: Cursor <cursoragent@cursor.com>
2026-02-21 15:46:06 -08:00

8.9 KiB

Deterministic Deployment Runbook (CREATE2)

Last Updated: 2026-01-31
Document Version: 1.0
Status: Active Documentation


This runbook defines how to deploy GRU multi-chain contracts at identical addresses across all supported EVM chains (138, CCIP chains, ALL Mainnet 651940). No per-chain address maps are allowed; the same contract address is used everywhere.

Principle

  • CREATE2 only for cross-chain contracts. Formula: address = keccak256(0xff ++ deployer ++ salt ++ keccak256(bytecode))[12:].
  • Identical bytecode on every chain (same compiler, no chain-specific code in bytecode).
  • Identical constructor args (or none) per contract type; any chain-specific config must be set in initialize() after deployment.
  • Fixed salts per contract name; documented below.

CREATE2 Factory

Canonical factory address

Use one of:

  1. Arachnid deterministic deployment proxy: 0x4e59b44847b379578588920cA78FbF26c0B4956C (exists on many chains).
  2. Project-owned factory: Deploy CREATE2Factory once per chain using the same salt on each chain so the factory itself has the same address everywhere. Recommended salt: keccak256("CREATE2Factory") or uint256(keccak256("CREATE2Factory")). See CREATE2Factory.sol.

If the canonical factory at 0x4e59b44847b379578588920cA78FbF26c0B4956C is not present on a chain (e.g. 138, 651940), deploy CREATE2Factory first via a one-time CREATE transaction, then use that factory address for all subsequent CREATE2 deployments. Document the factory address per chain in this runbook (e.g. in a table below) so it is identical where possible.

Factory address per chain (reference)

Chain Chain ID CREATE2 factory address
Ethereum 1 0x4e59b44847b379578588920cA78FbF26c0B4956C (or project-owned)
BNB Chain 56 0x4e59b44847b379578588920cA78FbF26c0B4956C (or project-owned)
Optimism 10 0x4e59b44847b379578588920cA78FbF26c0B4956C (or project-owned)
Polygon 137 0x4e59b44847b379578588920cA78FbF26c0B4956C (or project-owned)
Arbitrum One 42161 0x4e59b44847b379578588920cA78FbF26c0B4956C (or project-owned)
Base 8453 0x4e59b44847b379578588920cA78FbF26c0B4956C (or project-owned)
Avalanche 43114 0x4e59b44847b379578588920cA78FbF26c0B4956C (or project-owned)
DeFi Oracle Meta Mainnet 138 Deploy CREATE2Factory first; document address here
ALL Mainnet 651940 Deploy CREATE2Factory first; document address here

Contract → Salt and Constructor/Initializer Args

Salts must be fixed and documented. Use uint256(keccak256("ContractName")) or a named constant so the same value is used on every chain.

Contract Salt (use in CREATE2) Constructor / initializer args Notes
CREATE2Factory N/A (deploy with CREATE first on chains where canonical factory missing) None One-time; then use for all others.
UniversalCCIPBridge (proxy) keccak256("UniversalCCIPBridge") None (logic uses initialize(registry, router, admin)) Deploy proxy via factory; then call initialize() with same args on every chain.
MirrorRegistry keccak256("MirrorRegistry") _admin: same multisig or deterministic admin on all chains Must be identical _admin everywhere.
AlltraAdapter keccak256("AlltraAdapter") admin, _bridge: same admin and bridge address on all chains _bridge = UniversalCCIPBridge proxy address (same everywhere). After deploy, call setAlltraTransport(alltraCustomBridge) for 651940.
AlltraCustomBridge keccak256("AlltraCustomBridge") admin: same admin on 138 and 651940 For 138↔651940 transport (no CCIP). Deploy on 138 and 651940 at same address.
GRUCCIPBridge keccak256("GRUCCIPBridge") Inherits / uses UniversalCCIPBridge; deploy via factory with initializer Same pattern as UniversalCCIPBridge.
BridgeVault (interface impl) keccak256("BridgeVault") Same admin, policy manager, compliance registry on 1, 137, 56 For Bridge Vault chains (Eth, Polygon, BSC).
CompliantUSDC / cUSDC keccak256("CompliantUSDC") Same args if any (e.g. admin); or minimal proxy + init GRU-M1 base money.
CompliantUSDT / cUSDT keccak256("CompliantUSDT") Same as above GRU-M1 base money.
CompliantFiatToken (cEURC, cGBPC, cAUDC, etc.) keccak256("CompliantFiatToken.<symbol>") e.g. keccak256("CompliantFiatToken.cEURC") Same owner, admin, initialSupply; name/symbol/currencyCode per token Deploy via DeployCompliantFiatTokens.s.sol. Env: CREATE2_FACTORY_ADDRESS, PRIVATE_KEY, optional OWNER, ADMIN.
DepositToken (ac* impl) Per-currency salt e.g. keccak256("DepositToken.acUSDC") initializeWithDecimals(name, symbol, vault, collateralAsset, admin, decimals) — vault and admin same address everywhere; decimals e.g. 6 for stablecoins Aave-style asset token. Use deterministic vault address.
DebtToken (vdc* / sdc* impl) Per-currency salt e.g. keccak256("DebtToken.vdcUSDC") initializeFull(name, symbol, vault, currency, admin, decimals, transferable) — same Debt token.

Deployment Order (Dependencies First)

  1. CREATE2Factory (if not using canonical 0x4e59...): Deploy once per chain with CREATE; record address.
  2. UniversalAssetRegistry (if used by bridge): Deploy via CREATE2; salt e.g. keccak256("UniversalAssetRegistry"). Initialize with same admin.
  3. UniversalCCIPBridge (proxy): Deploy proxy via CREATE2; call initialize(assetRegistry, ccipRouter, admin) with chain-specific ccipRouter but same admin and assetRegistry address.
  4. MirrorRegistry: Deploy via CREATE2; constructor (admin) with same admin.
  5. AlltraAdapter: Deploy via CREATE2; constructor (admin, universalBridgeProxy) with same addresses.
  6. GRUCCIPBridge: Deploy via CREATE2 if separate from UniversalCCIPBridge; or deploy as implementation and proxy. Same admin/registry.
  7. BridgeVault: Deploy on chains 1, 137, 56 via CREATE2; same constructor args.
  8. Tokens (GRU-M1, ac, vdc)**: Deploy after vault/registry; use fixed salts and identical constructor/initializer args. Set chainId or chain-specific config only in initialize().

Verification

After deployment on each chain:

  • Run CREATE2Factory.computeAddress(bytecode, salt) (or equivalent) and confirm it matches the deployed address.
  • Record all deployed addresses in a single table (contract name → address); the address for each contract name must be identical across chains listed for that contract.
  • Do not introduce per-chain address maps in application code; use the single canonical address per contract.

Deployment Scripts

  • DeployDeterministicCore.s.sol: Forge script that deploys CREATE2Factory, UniversalAssetRegistry, UniversalCCIPBridge (impl + proxy), MirrorRegistry, and AlltraAdapter via CREATE2 with the fixed salts above. Env: PRIVATE_KEY, optional ADMIN. After deploy, call UniversalCCIPBridge.setCCIPRouter(router) on each chain.
  • DeployBridgeVaultDeterministic.s.sol: Deploy BridgeVault138 via CREATE2 with salt keccak256("BridgeVault") on chains 1, 137, 56 (Ethereum, Polygon, BSC). Env: PRIVATE_KEY, ADMIN, POLICY_MANAGER, COMPLIANCE_REGISTRY. Use identical args on all three chains so the vault address is the same. Configure cUSDT/cUSDC lock/unlock after deploy.
  • DeployCompliantFiatTokens.s.sol: Deploy CompliantFiatToken (cEURC, cEURT, cGBPC, cGBPT, cAUDC, cJPYC, cCHFC, cCADC, cXAUC, cXAUT) via CREATE2. Env: CREATE2_FACTORY_ADDRESS, PRIVATE_KEY, optional OWNER, ADMIN. Deploy CREATE2Factory first (e.g. via DeployDeterministicCore), then run this script so addresses match across chains.
  • DeployAcVdcSdcVaults.s.sol: Create all ac* / vdc* / sdc* vaults via VaultFactory.createVaultWithDecimals (one vault per base token: acUSDC+vdcUSDC, acUSDT+vdcUSDT). Run after DeployVaultSystem. Env: VAULT_FACTORY_ADDRESS, PRIVATE_KEY, optional OWNER, ENTITY; optional CUSDC_ADDRESS_138, CUSDT_ADDRESS_138 (or COMPLIANT_USDC_ADDRESS, COMPLIANT_USDT_ADDRESS). Uses 6 decimals and transferable debt. See CONTRACT_DEPLOYMENT_RUNBOOK § Vault ac* / vdc* / sdc*.

References