Skip to content

Commit

Permalink
feat: Support Zora (#1731)
Browse files Browse the repository at this point in the history
* feat: Support Zora
  • Loading branch information
pxrl authored Aug 14, 2024
1 parent 45c7863 commit d0749cf
Show file tree
Hide file tree
Showing 8 changed files with 135 additions and 149 deletions.
147 changes: 41 additions & 106 deletions contracts/AtomicWethDepositor.sol
Original file line number Diff line number Diff line change
@@ -1,13 +1,6 @@
// SPDX-License-Identifier: GPL-3.0-only
pragma solidity ^0.8.0;

import "@openzeppelin/contracts/access/Ownable.sol";
import "@uma/core/contracts/common/implementation/MultiCaller.sol";

///////////////////////////////
// Interfaces for Bridges //
///////////////////////////////

interface Weth {
function withdraw(uint256 _wad) external;

Expand Down Expand Up @@ -48,126 +41,68 @@ interface LineaL1MessageService {
* @notice Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain
* bridges for Optimism, Base, Boba, ZkSync, Linea, and Polygon. Needed as these chains only support bridging of ETH,
* not WETH.
* @dev This contract is ownable so that the owner can update the OvmL1Bridge contracts for new chains.
*/
contract AtomicWethDepositor is Ownable, MultiCaller {
///////////////////////////////
// Hardcoded Addresses //
///////////////////////////////

contract AtomicWethDepositor {
Weth public immutable weth = Weth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);
OvmL1Bridge public immutable optimismL1Bridge = OvmL1Bridge(0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1);
OvmL1Bridge public immutable modeL1Bridge = OvmL1Bridge(0x735aDBbE72226BD52e818E7181953f42E3b0FF21);
OvmL1Bridge public immutable bobaL1Bridge = OvmL1Bridge(0xdc1664458d2f0B6090bEa60A8793A4E66c2F1c00);
OvmL1Bridge public immutable baseL1Bridge = OvmL1Bridge(0x3154Cf16ccdb4C6d922629664174b904d80F2C35);
OvmL1Bridge public immutable liskL1Bridge = OvmL1Bridge(0x2658723Bf70c7667De6B25F99fcce13A16D25d08);
OvmL1Bridge public immutable redstoneL1Bridge = OvmL1Bridge(0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69);
OvmL1Bridge public immutable blastL1Bridge = OvmL1Bridge(0x697402166Fbf2F22E970df8a6486Ef171dbfc524);
OvmL1Bridge public immutable zoraL1Bridge = OvmL1Bridge(0x3e2Ea9B92B7E48A52296fD261dc26fd995284631);
PolygonL1Bridge public immutable polygonL1Bridge = PolygonL1Bridge(0xA0c68C638235ee32657e8f720a23ceC1bFc77C77);
ZkSyncL1Bridge public immutable zkSyncL1Bridge = ZkSyncL1Bridge(0x32400084C286CF3E17e7B677ea9583e60a000324);
LineaL1MessageService public immutable lineaL1MessageService =
LineaL1MessageService(0xd19d4B5d358258f05D7B411E21A1460D11B0876F);

///////////////////////////////
// Dynamic Variables //
///////////////////////////////

/**
* @notice All OVM Chains have an OvmL1Bridge contract that can be called to deposit ETH.
* @notice This mapping is a convenience to allow for easy lookup of the OvmL1Bridge contract for a given chainId.
*/
mapping(uint256 => OvmL1Bridge) public ovmChainIdToBridge;

///////////////////////////////
// Events //
///////////////////////////////

event ZkSyncEthDepositInitiated(address indexed from, address indexed to, uint256 amount);
event LineaEthDepositInitiated(address indexed from, address indexed to, uint256 amount);
event OVMEthDepositInitiated(uint256 indexed chainId, address indexed from, address indexed to, uint256 amount);
event PolygonEthDepositInitiated(address indexed from, address indexed to, uint256 amount);
event OVML1BridgeModified(uint256 indexed chainId, address indexed newBridge, address oldBridge);
event OvmEthDepositInitiated(uint256 indexed chainId, address indexed from, address indexed to, uint256 amount);

///////////////////////////////
// Errors //
///////////////////////////////

error InvalidOvmChainId();

///////////////////////////////
// Internal Functions //
///////////////////////////////

/**
* @notice Transfers WETH to this contract and withdraws it to ETH.
* @param amount The amount of WETH to withdraw.
*/
function _withdrawWeth(uint256 amount) internal {
function bridgeWethToOvm(address to, uint256 amount, uint32 l2Gas, uint256 chainId) public {
weth.transferFrom(msg.sender, address(this), amount);
weth.withdraw(amount);
}

///////////////////////////////
// Admin Functions //
///////////////////////////////

/**
* @notice Sets the OvmL1Bridge contract for a given chainId.
* @dev Supplying a zero address will disable the bridge.
* @param chainId The chainId of the OVM chain.
* @param newBridge The address of the OvmL1Bridge contract for the given chainId.
*/
function setOvmL1Bridge(uint256 chainId, OvmL1Bridge newBridge) public onlyOwner {
OvmL1Bridge oldBridge = ovmChainIdToBridge[chainId];
ovmChainIdToBridge[chainId] = newBridge;
emit OVML1BridgeModified(chainId, address(newBridge), address(oldBridge));
}

///////////////////////////////
// Public Functions //
///////////////////////////////

/**
* @notice Initiates a WETH deposit to an OVM chain.
* @param to The address on the OVM chain to receive the WETH.
* @param amount The amount of WETH to deposit.
* @param l2Gas The amount of gas to send with the deposit transaction.
* @param chainId The chainId of the OVM chain to deposit to.
* @dev throws InvalidOvmChainId if the chainId provided is not a valid OVM chainId.
*/
function bridgeWethToOvm(address to, uint256 amount, uint32 l2Gas, uint256 chainId) public {
OvmL1Bridge ovmL1Bridge = ovmChainIdToBridge[chainId];
if (address(ovmL1Bridge) == address(0)) {
revert InvalidOvmChainId();
if (chainId == 10) {
optimismL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 8453) {
baseL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 34443) {
modeL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 1135) {
liskL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 81457) {
blastL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 690) {
redstoneL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 7777777) {
zoraL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 288) {
bobaL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else {
revert("Invalid OVM chainId");
}
_withdrawWeth(amount);
ovmL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
emit OVMEthDepositInitiated(chainId, msg.sender, to, amount);

emit OvmEthDepositInitiated(chainId, msg.sender, to, amount);
}

/**
* @notice Initiates a WETH deposit to Polygon.
* @param to The address on Polygon to receive the WETH.
* @param amount The amount of WETH to deposit.
*/
function bridgeWethToPolygon(address to, uint256 amount) public {
_withdrawWeth(amount);
weth.transferFrom(msg.sender, address(this), amount);
weth.withdraw(amount);
polygonL1Bridge.depositEtherFor{ value: amount }(to);
emit PolygonEthDepositInitiated(msg.sender, to, amount);
}

/**
* @notice Initiates a WETH deposit to Linea.
* @param to The address on Linea to receive the WETH.
* @param amount The amount of WETH to deposit.
*/
function bridgeWethToLinea(address to, uint256 amount) public payable {
_withdrawWeth(amount);
weth.transferFrom(msg.sender, address(this), amount);
weth.withdraw(amount);
lineaL1MessageService.sendMessage{ value: amount + msg.value }(to, msg.value, "");
// Emit an event that we can easily track in the Linea-related adapters/finalizers
emit LineaEthDepositInitiated(msg.sender, to, amount);
}

/**
* @notice Initiates a WETH deposit to ZkSync.
* @param to The address on ZkSync to receive the WETH.
* @param amount The amount of WETH to deposit.
* @param l2GasLimit The amount of gas to send with the deposit transaction.
* @param l2GasPerPubdataByteLimit The amount of gas per pubdata byte to send with the deposit transaction.
* @param refundRecipient The address to refund any excess gas to.
*/
function bridgeWethToZkSync(
address to,
uint256 amount,
Expand All @@ -186,7 +121,8 @@ contract AtomicWethDepositor is Ownable, MultiCaller {
l2GasPerPubdataByteLimit
);
uint256 valueToSubmitXChainMessage = l2TransactionBaseCost + amount;
_withdrawWeth(valueToSubmitXChainMessage);
weth.transferFrom(msg.sender, address(this), valueToSubmitXChainMessage);
weth.withdraw(valueToSubmitXChainMessage);
zkSyncL1Bridge.requestL2Transaction{ value: valueToSubmitXChainMessage }(
to,
amount,
Expand All @@ -196,13 +132,12 @@ contract AtomicWethDepositor is Ownable, MultiCaller {
new bytes[](0),
refundRecipient
);

// Emit an event that we can easily track in the ZkSyncAdapter because otherwise there is no easy event to
// track ETH deposit initiations.
emit ZkSyncEthDepositInitiated(msg.sender, to, amount);
}

///////////////////////////////
// Fallback //
///////////////////////////////

fallback() external payable {}

// Included to remove a compilation warning.
Expand Down
41 changes: 27 additions & 14 deletions deployments/mainnet/AtomicWethDepositor.json

Large diffs are not rendered by default.

Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
{
"language": "Solidity",
"sources": {
"contracts/AtomicWethDepositor.sol": {
"content": "// SPDX-License-Identifier: GPL-3.0-only\npragma solidity ^0.8.0;\n\ninterface Weth {\n function withdraw(uint256 _wad) external;\n\n function transferFrom(address _from, address _to, uint256 _wad) external;\n}\n\ninterface OvmL1Bridge {\n function depositETHTo(address _to, uint32 _l2Gas, bytes calldata _data) external payable;\n}\n\ninterface PolygonL1Bridge {\n function depositEtherFor(address _to) external payable;\n}\n\ninterface ZkSyncL1Bridge {\n function requestL2Transaction(\n address _contractL2,\n uint256 _l2Value,\n bytes calldata _calldata,\n uint256 _l2GasLimit,\n uint256 _l2GasPerPubdataByteLimit,\n bytes[] calldata _factoryDeps,\n address _refundRecipient\n ) external payable;\n\n function l2TransactionBaseCost(\n uint256 _gasPrice,\n uint256 _l2GasLimit,\n uint256 _l2GasPerPubdataByteLimit\n ) external pure returns (uint256);\n}\n\ninterface LineaL1MessageService {\n function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;\n}\n\n/**\n * @notice Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain\n * bridges for Optimism, Base, Boba, ZkSync, Linea, and Polygon. Needed as these chains only support bridging of ETH,\n * not WETH.\n */\n\ncontract AtomicWethDepositor {\n Weth public immutable weth = Weth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);\n OvmL1Bridge public immutable optimismL1Bridge = OvmL1Bridge(0x99C9fc46f92E8a1c0deC1b1747d010903E884bE1);\n OvmL1Bridge public immutable modeL1Bridge = OvmL1Bridge(0x735aDBbE72226BD52e818E7181953f42E3b0FF21);\n OvmL1Bridge public immutable bobaL1Bridge = OvmL1Bridge(0xdc1664458d2f0B6090bEa60A8793A4E66c2F1c00);\n OvmL1Bridge public immutable baseL1Bridge = OvmL1Bridge(0x3154Cf16ccdb4C6d922629664174b904d80F2C35);\n OvmL1Bridge public immutable liskL1Bridge = OvmL1Bridge(0x2658723Bf70c7667De6B25F99fcce13A16D25d08);\n OvmL1Bridge public immutable redstoneL1Bridge = OvmL1Bridge(0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69);\n OvmL1Bridge public immutable blastL1Bridge = OvmL1Bridge(0x697402166Fbf2F22E970df8a6486Ef171dbfc524);\n OvmL1Bridge public immutable zoraL1Bridge = OvmL1Bridge(0x3e2Ea9B92B7E48A52296fD261dc26fd995284631);\n PolygonL1Bridge public immutable polygonL1Bridge = PolygonL1Bridge(0xA0c68C638235ee32657e8f720a23ceC1bFc77C77);\n ZkSyncL1Bridge public immutable zkSyncL1Bridge = ZkSyncL1Bridge(0x32400084C286CF3E17e7B677ea9583e60a000324);\n LineaL1MessageService public immutable lineaL1MessageService =\n LineaL1MessageService(0xd19d4B5d358258f05D7B411E21A1460D11B0876F);\n\n event ZkSyncEthDepositInitiated(address indexed from, address indexed to, uint256 amount);\n event LineaEthDepositInitiated(address indexed from, address indexed to, uint256 amount);\n event OvmEthDepositInitiated(uint256 indexed chainId, address indexed from, address indexed to, uint256 amount);\n\n function bridgeWethToOvm(address to, uint256 amount, uint32 l2Gas, uint256 chainId) public {\n weth.transferFrom(msg.sender, address(this), amount);\n weth.withdraw(amount);\n\n if (chainId == 10) {\n optimismL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 8453) {\n baseL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 34443) {\n modeL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 1135) {\n liskL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 81457) {\n blastL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 690) {\n redstoneL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 7777777) {\n zoraL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 288) {\n bobaL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else {\n revert(\"Invalid OVM chainId\");\n }\n\n emit OvmEthDepositInitiated(chainId, msg.sender, to, amount);\n }\n\n function bridgeWethToPolygon(address to, uint256 amount) public {\n weth.transferFrom(msg.sender, address(this), amount);\n weth.withdraw(amount);\n polygonL1Bridge.depositEtherFor{ value: amount }(to);\n }\n\n function bridgeWethToLinea(address to, uint256 amount) public payable {\n weth.transferFrom(msg.sender, address(this), amount);\n weth.withdraw(amount);\n lineaL1MessageService.sendMessage{ value: amount + msg.value }(to, msg.value, \"\");\n // Emit an event that we can easily track in the Linea-related adapters/finalizers\n emit LineaEthDepositInitiated(msg.sender, to, amount);\n }\n\n function bridgeWethToZkSync(\n address to,\n uint256 amount,\n uint256 l2GasLimit,\n uint256 l2GasPerPubdataByteLimit,\n address refundRecipient\n ) public {\n // The ZkSync Mailbox contract checks that the msg.value of the transaction is enough to cover the transaction base\n // cost. The transaction base cost can be queried from the Mailbox by passing in an L1 \"executed\" gas price,\n // which is the priority fee plus base fee. This is the same as calling tx.gasprice on-chain as the Mailbox\n // contract does here:\n // https://github.com/matter-labs/era-contracts/blob/3a4506522aaef81485d8abb96f5a6394bd2ba69e/ethereum/contracts/zksync/facets/Mailbox.sol#L287\n uint256 l2TransactionBaseCost = zkSyncL1Bridge.l2TransactionBaseCost(\n tx.gasprice,\n l2GasLimit,\n l2GasPerPubdataByteLimit\n );\n uint256 valueToSubmitXChainMessage = l2TransactionBaseCost + amount;\n weth.transferFrom(msg.sender, address(this), valueToSubmitXChainMessage);\n weth.withdraw(valueToSubmitXChainMessage);\n zkSyncL1Bridge.requestL2Transaction{ value: valueToSubmitXChainMessage }(\n to,\n amount,\n \"\",\n l2GasLimit,\n l2GasPerPubdataByteLimit,\n new bytes[](0),\n refundRecipient\n );\n\n // Emit an event that we can easily track in the ZkSyncAdapter because otherwise there is no easy event to\n // track ETH deposit initiations.\n emit ZkSyncEthDepositInitiated(msg.sender, to, amount);\n }\n\n fallback() external payable {}\n\n // Included to remove a compilation warning.\n // NOTE: this should not affect behavior.\n receive() external payable {}\n}\n"
}
},
"settings": {
"optimizer": {
"enabled": true,
"runs": 1000000
},
"viaIR": true,
"outputSelection": {
"*": {
"*": ["abi", "evm.bytecode", "evm.deployedBytecode", "evm.methodIdentifiers", "metadata"],
"": ["ast"]
}
}
}
}
5 changes: 3 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,8 +10,9 @@
"node": ">=20"
},
"dependencies": {
"@across-protocol/constants": "^3.1.12",
"@across-protocol/contracts": "^3.0.9",
"@across-protocol/constants": "^3.1.13",
"@across-protocol/contracts": "^3.0.10",
"@across-protocol/sdk": "^3.1.20",
"@across-protocol/sdk": "^3.1.21",
"@arbitrum/sdk": "^3.1.3",
"@consensys/linea-sdk": "^0.2.1",
Expand Down
11 changes: 10 additions & 1 deletion src/clients/bridges/AdapterManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ export class AdapterManager {
);
};

const { OPTIMISM, ARBITRUM, POLYGON, ZK_SYNC, BASE, MODE, LINEA, LISK, BLAST, REDSTONE, SCROLL } = CHAIN_IDs;
const { OPTIMISM, ARBITRUM, POLYGON, ZK_SYNC, BASE, MODE, LINEA, LISK, BLAST, REDSTONE, SCROLL, ZORA } = CHAIN_IDs;
if (this.spokePoolClients[OPTIMISM] !== undefined) {
this.adapters[OPTIMISM] = new OpStackAdapter(
OPTIMISM,
Expand Down Expand Up @@ -109,6 +109,15 @@ export class AdapterManager {
if (this.spokePoolClients[SCROLL] !== undefined) {
this.adapters[SCROLL] = new ScrollAdapter(logger, spokePoolClients, filterMonitoredAddresses(SCROLL));
}
if (this.spokePoolClients[ZORA] !== undefined) {
this.adapters[ZORA] = new OpStackAdapter(
ZORA,
logger,
SUPPORTED_TOKENS[ZORA],
spokePoolClients,
filterMonitoredAddresses(ZORA)
);
}

logger.debug({
at: "AdapterManager#constructor",
Expand Down
Loading

0 comments on commit d0749cf

Please sign in to comment.