Skip to content

chore: Update & deploy new WorldChain implementation #1014

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 2 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
31 changes: 3 additions & 28 deletions contracts/WorldChain_SpokePool.sol
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,6 @@ import "@eth-optimism/contracts/libraries/constants/Lib_PredeployAddresses.sol";

import "./Ovm_SpokePool.sol";
import "./external/interfaces/CCTPInterfaces.sol";
import { IOpUSDCBridgeAdapter } from "./external/interfaces/IOpUSDCBridgeAdapter.sol";

/**
* @notice World Chain Spoke pool.
Expand All @@ -13,9 +12,6 @@ import { IOpUSDCBridgeAdapter } from "./external/interfaces/IOpUSDCBridgeAdapter
contract WorldChain_SpokePool is Ovm_SpokePool {
using SafeERC20 for IERC20;

// Address of the custom L2 USDC bridge.
address private constant USDC_BRIDGE = 0xbD80b06d3dbD0801132c6689429aC09Ca6D27f82;

/// @custom:oz-upgrades-unsafe-allow constructor
constructor(
address _wrappedNativeTokenAddress,
Expand All @@ -34,11 +30,12 @@ contract WorldChain_SpokePool is Ovm_SpokePool {
{} // solhint-disable-line no-empty-blocks

/**
* @notice Construct the OVM World Chain SpokePool.
* @notice Construct the OVM SpokePool.
* @param _initialDepositId Starting deposit ID. Set to 0 unless this is a re-deployment in order to mitigate
* relay hash collisions.
* @param _crossDomainAdmin Cross domain admin to set. Can be changed by admin.
* @param _withdrawalRecipient Address which receives token withdrawals. Can be changed by admin. For Spoke Pools on L2, this will
* @param _withdrawalRecipient Address which receives token withdrawals. Can be changed by admin. For Spoke Pools on L2,
* this will likely be the hub pool.
*/
function initialize(
uint32 _initialDepositId,
Expand All @@ -47,26 +44,4 @@ contract WorldChain_SpokePool is Ovm_SpokePool {
) public initializer {
__OvmSpokePool_init(_initialDepositId, _crossDomainAdmin, _withdrawalRecipient, Lib_PredeployAddresses.OVM_ETH);
}

/**
* @notice World Chain-specific logic to bridge tokens back to the hub pool contract on L1.
* @param amountToReturn Amount of the token to bridge back.
* @param l2TokenAddress Address of the l2 Token to bridge back. This token will either be bridged back to the token defined in the mapping `remoteL1Tokens`,
* or via the canonical mapping defined in the bridge contract retrieved from `tokenBridges`.
* @dev This implementation deviates slightly from `_bridgeTokensToHubPool` in the `Ovm_SpokePool` contract since World Chain has a USDC bridge which uses
* a custom interface. This is because the USDC token on World Chain is meant to be upgraded to a native, CCTP supported version in the future.
*/
function _bridgeTokensToHubPool(uint256 amountToReturn, address l2TokenAddress) internal virtual override {
// Handle custom USDC bridge which doesn't conform to the standard bridge interface. In the future, CCTP may be used to bridge USDC to mainnet, in which
// case bridging logic is handled by the Ovm_SpokePool code. In the meantime, if CCTP is not enabled, then use the USDC bridge. Once CCTP is activated on
// WorldChain, this block of code will be unused.
if (l2TokenAddress == address(usdcToken) && !_isCCTPEnabled()) {
usdcToken.safeIncreaseAllowance(USDC_BRIDGE, amountToReturn);
IOpUSDCBridgeAdapter(USDC_BRIDGE).sendMessage(
withdrawalRecipient, // _to. Withdraw, over the bridge, to the l1 hub pool contract.
amountToReturn, // _amount.
l1Gas // _minGasLimit. Same value used in other OpStack bridges.
);
} else super._bridgeTokensToHubPool(amountToReturn, l2TokenAddress);
}
}
98 changes: 98 additions & 0 deletions contracts/chain-adapters/WorldChain_Adapter.sol
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// SPDX-License-Identifier: BUSL-1.1
pragma solidity ^0.8.0;

import "./interfaces/AdapterInterface.sol";
import "../external/interfaces/WETH9Interface.sol";

// @dev Use local modified CrossDomainEnabled contract instead of one exported by eth-optimism because we need
// this contract's state variables to be `immutable` because of the delegateCall call.
import "./CrossDomainEnabled.sol";
import "@eth-optimism/contracts/L1/messaging/IL1StandardBridge.sol";

import "@openzeppelin/contracts/token/ERC20/IERC20.sol";
import "@openzeppelin/contracts/token/ERC20/utils/SafeERC20.sol";

import "../libraries/CircleCCTPAdapter.sol";
import "../external/interfaces/CCTPInterfaces.sol";

/**
* @notice Contract containing logic to send messages from L1 to Doctor Who. This is a modified version of the Optimism adapter
* that excludes the custom bridging logic.
* @custom:security-contact [email protected]
* @dev Public functions calling external contracts do not guard against reentrancy because they are expected to be
* called via delegatecall, which will execute this contract's logic within the context of the originating contract.
* For example, the HubPool will delegatecall these functions, therefore its only necessary that the HubPool's methods
* that call this contract's logic guard against reentrancy.
*/

// solhint-disable-next-line contract-name-camelcase
contract WorldChain_Adapter is CrossDomainEnabled, AdapterInterface, CircleCCTPAdapter {
using SafeERC20 for IERC20;
uint32 public constant L2_GAS_LIMIT = 200_000;

WETH9Interface public immutable L1_WETH;

IL1StandardBridge public immutable L1_STANDARD_BRIDGE;

/**
* @notice Constructs new Adapter.
* @param _l1Weth WETH address on L1.
* @param _crossDomainMessenger XDomainMessenger Doctor Who system contract.
* @param _l1StandardBridge Standard bridge contract.
* @param _l1Usdc USDC address on L1.
* @param _cctpTokenMessenger TokenMessenger contract to bridge via CCTP.
*/
constructor(
WETH9Interface _l1Weth,
address _crossDomainMessenger,
IL1StandardBridge _l1StandardBridge,
IERC20 _l1Usdc,
ITokenMessenger _cctpTokenMessenger
)
CrossDomainEnabled(_crossDomainMessenger)
CircleCCTPAdapter(_l1Usdc, _cctpTokenMessenger, CircleDomainIds.WorldChain)
{
L1_WETH = _l1Weth;
L1_STANDARD_BRIDGE = _l1StandardBridge;
}

/**
* @notice Send cross-chain message to target on Doctor Who.
* @param target Contract on Doctor Who that will receive message.
* @param message Data to send to target.
*/
function relayMessage(address target, bytes calldata message) external payable override {
sendCrossDomainMessage(target, L2_GAS_LIMIT, message);
emit MessageRelayed(target, message);
}

/**
* @notice Bridge tokens to Doctor Who.
* @param l1Token L1 token to deposit.
* @param l2Token L2 token to receive.
* @param amount Amount of L1 tokens to deposit and L2 tokens to receive.
* @param to Bridge recipient.
*/
function relayTokens(
address l1Token,
address l2Token,
uint256 amount,
address to
) external payable override {
// If the l1Token is weth then unwrap it to ETH then send the ETH to the standard bridge.
if (l1Token == address(L1_WETH)) {
L1_WETH.withdraw(amount);
L1_STANDARD_BRIDGE.depositETHTo{ value: amount }(to, L2_GAS_LIMIT, "");
}
// Check if this token is USDC, which requires a custom bridge via CCTP.
else if (_isCCTPEnabled() && l1Token == address(usdcToken)) {
_transferUsdc(to, amount);
} else {
IL1StandardBridge _l1StandardBridge = L1_STANDARD_BRIDGE;

IERC20(l1Token).safeIncreaseAllowance(address(_l1StandardBridge), amount);
_l1StandardBridge.depositERC20To(l1Token, l2Token, to, amount, L2_GAS_LIMIT, "");
}
emit TokensRelayed(l1Token, l2Token, amount, to);
}
}
1 change: 1 addition & 0 deletions contracts/libraries/CircleCCTPAdapter.sol
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,7 @@ library CircleDomainIds {
uint32 public constant Polygon = 7;
uint32 public constant DoctorWho = 10;
uint32 public constant Linea = 11;
uint32 public constant WorldChain = 14;
uint32 public constant UNINITIALIZED = type(uint32).max;
}

Expand Down
28 changes: 15 additions & 13 deletions deploy/050_deploy_worldchain_adapter.ts
Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nb. I wasn't successful in verifying via this adapter due to the Etherscan v2 migration. I ended up having to deploy & verify with foundry instead, so this deployment script is mostly for documentation.

Original file line number Diff line number Diff line change
@@ -1,27 +1,29 @@
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { DeployFunction } from "hardhat-deploy/types";
import { CHAIN_IDs } from "../utils";
import { OP_STACK_ADDRESS_MAP, USDC, WETH } from "./consts";

const SPOKE_CHAIN_ID = CHAIN_IDs.WORLD_CHAIN;
import { L1_ADDRESS_MAP, OP_STACK_ADDRESS_MAP, USDC, WETH } from "./consts";

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const spokeChainId = Number(process.env.SPOKE_CHAIN_ID ?? CHAIN_IDs.WORLD_CHAIN);
const { deployer } = await hre.getNamedAccounts();
const chainId = parseInt(await hre.getChainId());
const opStack = OP_STACK_ADDRESS_MAP[chainId][SPOKE_CHAIN_ID];
const opStack = OP_STACK_ADDRESS_MAP[chainId][spokeChainId];

const args = [
WETH[chainId],
opStack.L1CrossDomainMessenger,
opStack.L1StandardBridge,
USDC[chainId],
L1_ADDRESS_MAP[chainId].cctpV2TokenMessenger,
];

await hre.deployments.deploy("OP_Adapter", {
const instance = await hre.deployments.deploy("WorldChain_Adapter", {
from: deployer,
log: true,
skipIfAlreadyDeployed: true,
args: [
WETH[chainId],
USDC[chainId],
opStack.L1CrossDomainMessenger,
opStack.L1StandardBridge,
opStack.L1OpUSDCBridge,
],
skipIfAlreadyDeployed: false,
args,
});
await hre.run("verify:verify", { address: instance.address, constructorArguments: args });
};

module.exports = func;
Expand Down
12 changes: 3 additions & 9 deletions deploy/051_deploy_worldchain_spokepool.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
import { DeployFunction } from "hardhat-deploy/types";
import { HardhatRuntimeEnvironment } from "hardhat/types";
import { deployNewProxy, getSpokePoolDeploymentInfo } from "../utils/utils.hre";
import { FILL_DEADLINE_BUFFER, WETH, QUOTE_TIME_BUFFER, ZERO_ADDRESS, USDCe } from "./consts";
import { FILL_DEADLINE_BUFFER, L2_ADDRESS_MAP, QUOTE_TIME_BUFFER, USDC, WETH } from "./consts";

const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
const { hubPool, spokeChainId } = await getSpokePoolDeploymentInfo(hre);
Expand All @@ -16,14 +16,8 @@ const func: DeployFunction = async function (hre: HardhatRuntimeEnvironment) {
WETH[spokeChainId],
QUOTE_TIME_BUFFER,
FILL_DEADLINE_BUFFER,
// World Chain's bridged USDC is upgradeable to native. There are not two different
// addresses for bridges/native USDC. This address is also used in the spoke pool
// to determine whether to use CCTP (in the future) or the custom USDC bridge.
USDCe[spokeChainId],
// L2_ADDRESS_MAP[spokeChainId].cctpTokenMessenger,
// For now, we are not using the CCTP bridge and can disable by setting
// the cctpTokenMessenger to the zero address.
ZERO_ADDRESS,
USDC[spokeChainId],
L2_ADDRESS_MAP[spokeChainId].cctpTokenMessenger,
];
await deployNewProxy("WorldChain_SpokePool", constructorArgs, initArgs);
};
Expand Down
5 changes: 4 additions & 1 deletion deploy/consts.ts
Original file line number Diff line number Diff line change
Expand Up @@ -127,7 +127,6 @@ export const OP_STACK_ADDRESS_MAP: {
[CHAIN_IDs.WORLD_CHAIN]: {
L1CrossDomainMessenger: "0xf931a81D18B1766d15695ffc7c1920a62b7e710a",
L1StandardBridge: "0x470458C91978D2d929704489Ad730DC3E3001113",
L1OpUSDCBridgeAdapter: "0x153A69e4bb6fEDBbAaF463CB982416316c84B2dB",
},
[CHAIN_IDs.ZORA]: {
L1CrossDomainMessenger: "0xdC40a14d9abd6F410226f1E6de71aE03441ca506",
Expand Down Expand Up @@ -257,6 +256,10 @@ export const L2_ADDRESS_MAP: { [key: number]: { [contractName: string]: string }
cctpTokenMessenger: "0x8ed94B8dAd2Dc5453862ea5e316A8e71AAed9782",
cctpMessageTransmitter: "0xbc498c326533d675cf571B90A2Ced265ACb7d086",
},
[CHAIN_IDs.WORLD_CHAIN]: {
cctpTokenMessenger: "0x28b5a0e9C621a5BadaA536219b3a228C8168cf5d",
cctpMessageTransmitter: "0x81D40F21F12A8F0E3252Bccb954D722d4c464B64",
},
};

export const POLYGON_CHAIN_IDS: { [l1ChainId: number]: number } = {
Expand Down
2 changes: 1 addition & 1 deletion deployments/deployments.json
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,7 @@
"Blast_RescueAdapter": { "address": "0xE5Dea263511F5caC27b15cBd58Ff103F4Ce90957", "blockNumber": 20378872 },
"Redstone_Adapter": { "address": "0x188F8C95B7cfB7993B53a4F643efa687916f73fA", "blockNumber": 20432774 },
"Zora_Adapter": { "address": "0x024f2fc31cbdd8de17194b1892c834f98ef5169b", "blockNumber": 20512287 },
"WorldChain_Adapter": { "address": "0xA8399e221a583A57F54Abb5bA22f31b5D6C09f32", "blockNumber": 20963234 },
"WorldChain_Adapter": { "address": "0x8bbdD67102D743b8533c1277a4ffdA04Dea158D1", "blockNumber": 22626594 },
"AlephZero_Adapter": { "address": "0x6F4083304C2cA99B077ACE06a5DcF670615915Af", "blockNumber": 21131132 },
"Ink_Adapter": { "address": "0x7e90a40c7519b041a7df6498fbf5662e8cfc61d2", "blockNumber": 21438590 },
"Cher_Adapter": { "address": "0x0c9d064523177dBB55CFE52b9D0c485FBFc35FD2", "blockNumber": 21597341 },
Expand Down
85 changes: 19 additions & 66 deletions deployments/worldchain/WorldChain_SpokePool.json

Large diffs are not rendered by default.

Loading
Loading