From 41f6cda3e614d0bbda62939094786f1c100f7ab2 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 19 Nov 2025 10:47:54 -0500 Subject: [PATCH] feat: Add Forge deploy script for Universal contracts Add adapter and spoke pool forge scripts --- deploy/consts.ts | 1 + generated/constants.json | 1 + script/110DeployUniversalAdapter.s.sol | 71 +++++++++++++++++ script/111DeployUniversalSpokePool.s.sol | 98 ++++++++++++++++++++++++ script/utils/DeploymentUtils.sol | 2 +- 5 files changed, 172 insertions(+), 1 deletion(-) create mode 100644 script/110DeployUniversalAdapter.s.sol create mode 100644 script/111DeployUniversalSpokePool.s.sol diff --git a/deploy/consts.ts b/deploy/consts.ts index 55aaf100e..d6634e61f 100644 --- a/deploy/consts.ts +++ b/deploy/consts.ts @@ -206,6 +206,7 @@ export const L2_ADDRESS_MAP: { [key: number]: { [contractName: string]: string } permit2: "0x000000000022D473030F116dDEE9F6B43aC78BA3", }, [CHAIN_IDs.MONAD]: { + helios: "0x09aea4b2242abc8bb4bb78d537a67a245a7bec64", cctpV2TokenMessenger: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d", }, [CHAIN_IDs.PLASMA]: { diff --git a/generated/constants.json b/generated/constants.json index 488ac0540..d41261354 100644 --- a/generated/constants.json +++ b/generated/constants.json @@ -609,6 +609,7 @@ "permit2": "0x000000000022D473030F116dDEE9F6B43aC78BA3" }, "143": { + "helios": "0x09aea4b2242abc8bb4bb78d537a67a245a7bec64", "cctpV2TokenMessenger": "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d" }, "232": { diff --git a/script/110DeployUniversalAdapter.s.sol b/script/110DeployUniversalAdapter.s.sol new file mode 100644 index 000000000..f7658fb38 --- /dev/null +++ b/script/110DeployUniversalAdapter.s.sol @@ -0,0 +1,71 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { Script } from "forge-std/Script.sol"; +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; +import { Universal_Adapter } from "../contracts/chain-adapters/Universal_Adapter.sol"; +import { HubPoolStore } from "../contracts/chain-adapters/utilities/HubPoolStore.sol"; +import { Constants } from "./utils/Constants.sol"; +import { IERC20 } from "@openzeppelin/contracts-v4/token/ERC20/IERC20.sol"; +import { ITokenMessenger } from "../contracts/external/interfaces/CCTPInterfaces.sol"; +import { WETH9Interface } from "../contracts/external/interfaces/WETH9Interface.sol"; + +// How to run: +// 1. `source .env` where `.env` has MNEMONIC="x x x ... x" and ETHERSCAN_API_KEY="x" entries +// 2. forge script script/110DeployUniversalAdapter.s.sol:DeployUniversalAdapter --sig "run(string)" --rpc-url $NODE_URL_1 -vvvv +// 3. Verify the above works in simulation mode. +// 4. Deploy on mainnet by adding --broadcast --verify flags. +// 5. forge script script/110DeployUniversalAdapter.s.sol:DeployUniversalAdapter MONAD --rpc-url $NODE_URL_1 --broadcast --verify -vvvv + +contract DeployUniversalAdapter is Script, Test, Constants { + function run() external pure { + revert("Not implemented, see script for run instructions"); + } + function run(string calldata destinationChainName) external { + string memory deployerMnemonic = vm.envString("MNEMONIC"); + uint256 deployerPrivateKey = vm.deriveKey(deployerMnemonic, 0); + + // Get the current chain ID + uint256 chainId = block.chainid; + + // Verify this is being deployed on Ethereum mainnet or Sepolia + require( + chainId == getChainId("MAINNET") || chainId == getChainId("SEPOLIA"), + "Universal_Adapter should only be deployed on Ethereum mainnet or Sepolia" + ); + + uint256 destinationChainId = getChainId(destinationChainName); + bool hasCctpDomain = hasCctpDomain(destinationChainId); + address cctpTokenMessenger = hasCctpDomain ? getL1Addresses(chainId).cctpV2TokenMessenger : address(0); + uint32 cctpDomainId = hasCctpDomain ? uint32(getCircleDomainId(destinationChainId)) : 0; + + uint256 oftFeeCap = 1 ether; + + vm.startBroadcast(deployerPrivateKey); + + // Deploy Universal_Adapter with constructor parameters + Universal_Adapter universalAdapter = new Universal_Adapter( + HubPoolStore(getL1Addresses(chainId).hubPoolStore), + IERC20(getUSDCAddress(chainId)), + ITokenMessenger(cctpTokenMessenger), + cctpDomainId, + getL1Addresses(chainId).adapterStore, + uint32(getOftEid(destinationChainId)), + oftFeeCap + ); + + // Log the deployed addresses + console.log("Chain ID:", chainId); + console.log("Universal_Adapter deployed to:", address(universalAdapter)); + console.log("L1 HubPoolStore:", getL1Addresses(chainId).hubPoolStore); + console.log("L1 AdapterStore:", getL1Addresses(chainId).adapterStore); + console.log("L1 USDC:", getUSDCAddress(chainId)); + console.log("CCTP Token Messenger:", cctpTokenMessenger); + console.log("CCTP Domain ID:", cctpDomainId); + console.log("OFT Destination EID:", getOftEid(destinationChainId)); + console.log("OFT Fee Cap:", oftFeeCap); + + vm.stopBroadcast(); + } +} diff --git a/script/111DeployUniversalSpokePool.s.sol b/script/111DeployUniversalSpokePool.s.sol new file mode 100644 index 000000000..1fbaeb339 --- /dev/null +++ b/script/111DeployUniversalSpokePool.s.sol @@ -0,0 +1,98 @@ +// SPDX-License-Identifier: BUSL-1.1 +pragma solidity ^0.8.0; + +import { Script } from "forge-std/Script.sol"; +import { Test } from "forge-std/Test.sol"; +import { console } from "forge-std/console.sol"; +import { Universal_SpokePool } from "../contracts/Universal_SpokePool.sol"; +import { DeploymentUtils } from "./utils/DeploymentUtils.sol"; +import { ITokenMessenger } from "../contracts/external/interfaces/CCTPInterfaces.sol"; + +// How to run: +// 1. `source .env` where `.env` has MNEMONIC="x x x ... x" +// 2. forge script script/111DeployUniversalSpokePool.s.sol:DeployUniversalSpokePool --sig "run(uint256)" --rpc-url $NODE_URL_143 -vvvv +// 3. Verify the above works in simulation mode. +// 4. Deploy with: +// forge script script/110DeployUniversalAdapter.s.sol:DeployUniversalAdapter --sig "run(uint256)" --rpc-url \ +// $NODE_URL_143 --broadcast --verifier @todo --verifier-url @todo + +contract DeployUniversalSpokePool is Script, Test, DeploymentUtils { + function run() external pure { + revert("Not implemented, see script for run instructions"); + } + function run(uint256 oftFeeCap) external { + string memory deployerMnemonic = vm.envString("MNEMONIC"); + uint256 deployerPrivateKey = vm.deriveKey(deployerMnemonic, 0); + + // Get deployment information + DeploymentInfo memory info = getSpokePoolDeploymentInfo(address(0)); + + // Get the appropriate addresses for this chain + address wrappedNativeToken = getWrappedNativeToken(info.spokeChainId); + + // Get USDC address for this chain + address usdcAddress = getUSDCAddress(info.spokeChainId); + + vm.startBroadcast(deployerPrivateKey); + + uint256 heliosAdminBufferUpdateSeconds = 1 days; + address helios = getL2Address(info.spokeChainId, "helios"); + address l1HubPoolStore = getL1Addresses(info.hubChainId).hubPoolStore; + + bool hasCctpDomain = hasCctpDomain(info.spokeChainId); + address cctpTokenMessenger = hasCctpDomain + ? getL2Address(info.spokeChainId, "cctpV2TokenMessenger") + : address(0); + uint32 oftDstEid = uint32(getOftEid(info.hubChainId)); + + // Prepare constructor arguments for Universal_SpokePool + bytes memory constructorArgs = abi.encode( + heliosAdminBufferUpdateSeconds, + helios, + l1HubPoolStore, + wrappedNativeToken, + QUOTE_TIME_BUFFER(), // _depositQuoteTimeBuffer + FILL_DEADLINE_BUFFER(), // _fillDeadlineBuffer + usdcAddress, + cctpTokenMessenger, + oftDstEid, + oftFeeCap + ); + + // Initialize deposit counter to 1 + // Set hub pool as cross domain admin since it delegatecalls the Adapter logic. + bytes memory initArgs = abi.encodeWithSelector( + Universal_SpokePool.initialize.selector, + 1, // _initialDepositId + info.hubPool, // _crossDomainAdmin + info.hubPool // _withdrawalRecipient + ); + + // Deploy the proxy + DeploymentResult memory result = deployNewProxy( + "Universal_SpokePool", + constructorArgs, + initArgs, + true // implementationOnly + ); + + // Log the deployed addresses + console.log("Chain ID:", info.spokeChainId); + console.log("Hub Chain ID:", info.hubChainId); + console.log("HubPool address:", info.hubPool); + console.log("Helios address:", helios); + console.log("L1 HubPoolStore address:", l1HubPoolStore); + console.log("Wrapped Native Token address:", weth); + console.log("USDC address:", usdcAddress); + console.log("CCTP Token Messenger:", cctpTokenMessenger); + console.log("OFT DST EID:", oftDstEid); + console.log("OFT Fee Cap:", oftFeeCap); + console.log("Universal_SpokePool proxy deployed to:", result.proxy); + console.log("Universal_SpokePool implementation deployed to:", result.implementation); + + console.log("QUOTE_TIME_BUFFER()", QUOTE_TIME_BUFFER()); + console.log("FILL_DEADLINE_BUFFER()", FILL_DEADLINE_BUFFER()); + + vm.stopBroadcast(); + } +} diff --git a/script/utils/DeploymentUtils.sol b/script/utils/DeploymentUtils.sol index 11b2fcc7d..845420608 100644 --- a/script/utils/DeploymentUtils.sol +++ b/script/utils/DeploymentUtils.sol @@ -41,7 +41,6 @@ contract DeploymentUtils is Script, Test, Constants, DeployedAddresses { * @return info Deployment information struct */ function getSpokePoolDeploymentInfo(address hubPoolAddress) public view returns (DeploymentInfo memory info) { - console.log("hubPoolAddress", hubPoolAddress); uint256 spokeChainId = block.chainid; // Determine hub chain ID based on spoke chain ID @@ -60,6 +59,7 @@ contract DeploymentUtils is Script, Test, Constants, DeployedAddresses { if (hubPool == address(0)) { hubPool = getAddress(hubChainId, "HubPool"); } + console.log("hubPoolAddress", hubPool); require(hubPool != address(0), "HubPool address cannot be zero");