Skip to content
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

feat: Support Zora #1731

Merged
merged 10 commits into from
Aug 14, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
147 changes: 41 additions & 106 deletions contracts/AtomicWethDepositor.sol
pxrl marked this conversation as resolved.
Show resolved Hide resolved
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);
pxrl marked this conversation as resolved.
Show resolved Hide resolved
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"]
}
}
}
}
6 changes: 3 additions & 3 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -10,9 +10,9 @@
"node": ">=20"
},
"dependencies": {
"@across-protocol/constants": "^3.1.12",
"@across-protocol/contracts": "^3.0.9",
"@across-protocol/sdk": "^3.1.18",
"@across-protocol/constants": "^3.1.13",
"@across-protocol/contracts": "^3.0.10",
"@across-protocol/sdk": "^3.1.20",
"@arbitrum/sdk": "^3.1.3",
"@consensys/linea-sdk": "^0.2.1",
"@defi-wonderland/smock": "^2.3.5",
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