Skip to content

Commit

Permalink
Sync with upstream repo (#83)
Browse files Browse the repository at this point in the history
Merge latest changes from upstream repo
  • Loading branch information
sameersubudhi authored Jan 3, 2025
2 parents bd057d0 + 85a809e commit 823bd96
Show file tree
Hide file tree
Showing 43 changed files with 3,325 additions and 1,173 deletions.
2 changes: 1 addition & 1 deletion CODEOWNERS
Validating CODEOWNERS rules …
Original file line number Diff line number Diff line change
Expand Up @@ -6,4 +6,4 @@

# These owners will be the default owners for everything in
# the repo unless a later match takes precedence.
* @mrice32 @nicholaspai @pxrl @james-a-morris
* @mrice32 @nicholaspai @pxrl @james-a-morris @dohaki
200 changes: 125 additions & 75 deletions contracts/AtomicWethDepositor.sol
Original file line number Diff line number Diff line change
@@ -1,97 +1,147 @@
// 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";
import "@uma/core/contracts/common/implementation/Lockable.sol";

interface Weth {
function withdraw(uint256 _wad) external;

function transferFrom(address _from, address _to, uint256 _wad) external;
}

/**
* @notice Contract deployed on Ethereum helps relay bots atomically unwrap and bridge WETH over the canonical chain
* bridges for chains that only support bridging of ETH not WETH.
* @dev This contract is ownable so that the owner can update whitelisted bridge addresses and function selectors.
*/
contract AtomicWethDepositor is Ownable, MultiCaller, Lockable {
// The Bridge used to send ETH to another chain. Only the function selector can be used when
// calling the bridge contract.
struct Bridge {
address bridge;
bytes4 funcSelector;
}

Weth public immutable WETH = Weth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

/**
* @notice Mapping of chain ID to whitelisted bridge addresses and function selectors
* that can be called by this contract.
*/
mapping(uint256 => Bridge) public whitelistedBridgeFunctions;
interface OvmL1Bridge {
function depositETHTo(address _to, uint32 _l2Gas, bytes calldata _data) external payable;
}

///////////////////////////////
// Events //
///////////////////////////////
interface PolygonL1Bridge {
function depositEtherFor(address _to) external payable;
}

event AtomicWethDepositInitiated(address indexed from, uint256 indexed chainId, uint256 amount);
interface ZkSyncL1Bridge {
function requestL2Transaction(
address _contractL2,
uint256 _l2Value,
bytes calldata _calldata,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit,
bytes[] calldata _factoryDeps,
address _refundRecipient
) external payable;

function l2TransactionBaseCost(
uint256 _gasPrice,
uint256 _l2GasLimit,
uint256 _l2GasPerPubdataByteLimit
) external pure returns (uint256);
}

///////////////////////////////
// Errors //
///////////////////////////////
interface LineaL1MessageService {
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;
}

error InvalidBridgeFunction();
/**
* @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.
*/

///////////////////////////////
// Internal Functions //
///////////////////////////////
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 inkL1Bridge = OvmL1Bridge(0x88FF1e5b602916615391F55854588EFcBB7663f0);
OvmL1Bridge public immutable liskL1Bridge = OvmL1Bridge(0x2658723Bf70c7667De6B25F99fcce13A16D25d08);
OvmL1Bridge public immutable redstoneL1Bridge = OvmL1Bridge(0xc473ca7E02af24c129c2eEf51F2aDf0411c1Df69);
OvmL1Bridge public immutable blastL1Bridge = OvmL1Bridge(0x697402166Fbf2F22E970df8a6486Ef171dbfc524);
OvmL1Bridge public immutable worldChainL1Bridge = OvmL1Bridge(0x470458C91978D2d929704489Ad730DC3E3001113);
OvmL1Bridge public immutable zoraL1Bridge = OvmL1Bridge(0x3e2Ea9B92B7E48A52296fD261dc26fd995284631);
PolygonL1Bridge public immutable polygonL1Bridge = PolygonL1Bridge(0xA0c68C638235ee32657e8f720a23ceC1bFc77C77);
ZkSyncL1Bridge public immutable zkSyncL1Bridge = ZkSyncL1Bridge(0x32400084C286CF3E17e7B677ea9583e60a000324);
LineaL1MessageService public immutable lineaL1MessageService =
LineaL1MessageService(0xd19d4B5d358258f05D7B411E21A1460D11B0876F);

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);

function bridgeWethToOvm(address to, uint256 amount, uint32 l2Gas, uint256 chainId) public {
weth.transferFrom(msg.sender, address(this), amount);
weth.withdraw(amount);

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 == 480) {
worldChainL1Bridge.depositETHTo{ value: amount }(to, l2Gas, "");
} else if (chainId == 57073) {
inkL1Bridge.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");
}

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

/**
* @notice Transfers WETH to this contract and withdraws it to ETH.
* @param amount The amount of WETH to withdraw.
*/
function _withdrawWeth(uint256 amount) internal {
WETH.transferFrom(msg.sender, address(this), amount);
WETH.withdraw(amount);
function bridgeWethToPolygon(address to, uint256 amount) public {
weth.transferFrom(msg.sender, address(this), amount);
weth.withdraw(amount);
polygonL1Bridge.depositEtherFor{ value: amount }(to);
}

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

/**
* @notice Whitelists function selector and bridge contract for chain.
* @param chainId The chain ID of the bridge.
* @param bridge The address of the bridge contract to call to bridge ETH to the chain.
* @param funcSelector The function selector of the bridge contract.
*/
function whitelistBridge(uint256 chainId, address bridge, bytes4 funcSelector) public onlyOwner {
whitelistedBridgeFunctions[chainId] = Bridge({ bridge: bridge, funcSelector: funcSelector });
function bridgeWethToLinea(address to, uint256 amount) public payable {
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);
}

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

/**
* @notice Initiates a WETH deposit to a whitelisted bridge for a specified chain with user calldata.
* @dev Requires that the owner of this contract has whitelisted the bridge contract and function
* selector for the chainId that the user wants to send ETH to.
* @param value The amount of WETH to deposit.
* @param chainId The chain to send ETH to.
* @param bridgeCallData The calldata to pass to the bridge contract. The first 4 bytes should be equal
* to the whitelisted function selector of the bridge contract.
*/
function bridgeWeth(uint256 chainId, uint256 value, bytes calldata bridgeCallData) public nonReentrant {
_withdrawWeth(value);
Bridge memory bridge = whitelistedBridgeFunctions[chainId];
if (bridge.funcSelector != bytes4(bridgeCallData)) revert InvalidBridgeFunction();
// solhint-disable-next-line avoid-low-level-calls
(bool success, bytes memory result) = bridge.bridge.call{ value: value }(bridgeCallData);
require(success, string(result));
emit AtomicWethDepositInitiated(msg.sender, chainId, value);
function bridgeWethToZkSync(
address to,
uint256 amount,
uint256 l2GasLimit,
uint256 l2GasPerPubdataByteLimit,
address refundRecipient
) public {
// The ZkSync Mailbox contract checks that the msg.value of the transaction is enough to cover the transaction base
// cost. The transaction base cost can be queried from the Mailbox by passing in an L1 "executed" gas price,
// which is the priority fee plus base fee. This is the same as calling tx.gasprice on-chain as the Mailbox
// contract does here:
// https://github.com/matter-labs/era-contracts/blob/3a4506522aaef81485d8abb96f5a6394bd2ba69e/ethereum/contracts/zksync/facets/Mailbox.sol#L287
uint256 l2TransactionBaseCost = zkSyncL1Bridge.l2TransactionBaseCost(
tx.gasprice,
l2GasLimit,
l2GasPerPubdataByteLimit
);
uint256 valueToSubmitXChainMessage = l2TransactionBaseCost + amount;
weth.transferFrom(msg.sender, address(this), valueToSubmitXChainMessage);
weth.withdraw(valueToSubmitXChainMessage);
zkSyncL1Bridge.requestL2Transaction{ value: valueToSubmitXChainMessage }(
to,
amount,
"",
l2GasLimit,
l2GasPerPubdataByteLimit,
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() external payable {}
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 inkL1Bridge = OvmL1Bridge(0x88FF1e5b602916615391F55854588EFcBB7663f0);\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 worldChainL1Bridge = OvmL1Bridge(0x470458C91978D2d929704489Ad730DC3E3001113);\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 == 480) {\n worldChainL1Bridge.depositETHTo{ value: amount }(to, l2Gas, \"\");\n } else if (chainId == 57073) {\n inkL1Bridge.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"]
}
}
}
}
16 changes: 13 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.22",
"@across-protocol/contracts": "^3.0.18",
"@across-protocol/sdk": "^3.3.25",
"@across-protocol/constants": "^3.1.25",
"@across-protocol/contracts": "^3.0.19",
"@across-protocol/sdk": "^3.3.32",
"@arbitrum/sdk": "^4.0.2",
"@aws-sdk/client-kms": "^3.592.0",
"@aws-sdk/client-s3": "^3.592.0",
Expand Down Expand Up @@ -117,5 +117,15 @@
"publishConfig": {
"registry": "https://registry.npmjs.com/",
"access": "public"
},
"resolutions": {
"secp256k1": "4.0.4",
"**/secp256k1": "4.0.4",
"eccrypto/secp256k1": "3.8.1"
},
"overrides": {
"[email protected]": "3.8.1",
"[email protected]": "4.0.4",
"[email protected]": "5.0.1"
}
}
Loading

0 comments on commit 823bd96

Please sign in to comment.