Co-authored-by: Cursor <cursoragent@cursor.com>
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:
- Arachnid deterministic deployment proxy:
0x4e59b44847b379578588920cA78FbF26c0B4956C(exists on many chains). - 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")oruint256(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)
- CREATE2Factory (if not using canonical 0x4e59...): Deploy once per chain with CREATE; record address.
- UniversalAssetRegistry (if used by bridge): Deploy via CREATE2; salt e.g.
keccak256("UniversalAssetRegistry"). Initialize with same admin. - UniversalCCIPBridge (proxy): Deploy proxy via CREATE2; call
initialize(assetRegistry, ccipRouter, admin)with chain-specificccipRouterbut sameadminandassetRegistryaddress. - MirrorRegistry: Deploy via CREATE2; constructor
(admin)with same admin. - AlltraAdapter: Deploy via CREATE2; constructor
(admin, universalBridgeProxy)with same addresses. - GRUCCIPBridge: Deploy via CREATE2 if separate from UniversalCCIPBridge; or deploy as implementation and proxy. Same admin/registry.
- BridgeVault: Deploy on chains 1, 137, 56 via CREATE2; same constructor args.
- 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, optionalADMIN. After deploy, callUniversalCCIPBridge.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, optionalOWNER,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, optionalOWNER,ENTITY; optionalCUSDC_ADDRESS_138,CUSDT_ADDRESS_138(orCOMPLIANT_USDC_ADDRESS,COMPLIANT_USDT_ADDRESS). Uses 6 decimals and transferable debt. See CONTRACT_DEPLOYMENT_RUNBOOK § Vault ac* / vdc* / sdc*.