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

improve: use new atomic depositor in adapters #1852

Open
wants to merge 45 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from 21 commits
Commits
Show all changes
45 commits
Select commit Hold shift + click to select a range
0be4dae
feat: Create generic AtomicWethDepositor
nicholaspai Jul 22, 2024
8390646
Update AtomicWethDepositor.sol
nicholaspai Jul 22, 2024
c3ab88c
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 22, 2024
83ac986
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 22, 2024
8d1b9bf
whitelistedBridgeFunctions
nicholaspai Jul 23, 2024
6506fa4
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 23, 2024
c0293fb
Update AtomicWethDepositor.sol
nicholaspai Jul 26, 2024
bf92a2e
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 26, 2024
dc11552
Update AtomicWethDepositor.sol
nicholaspai Jul 26, 2024
21c2881
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 29, 2024
4eb669c
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 29, 2024
5756204
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 29, 2024
53cd16d
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Jul 30, 2024
ca6aa2b
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Aug 20, 2024
46028ef
Merge branch 'master' into npai/atomic-weth-depositor
nicholaspai Aug 21, 2024
b521d23
Merge branch 'master' into npai/atomic-weth-depositor
bmzig Oct 7, 2024
b05d76d
improve: use new atomic depositor
bmzig Oct 7, 2024
13168f9
merge
pxrl Oct 25, 2024
5544e81
Revert "merge"
pxrl Oct 25, 2024
9ecfc25
Merge remote-tracking branch 'origin/master' into npai/atomic-weth-de…
pxrl Oct 25, 2024
7f384c8
Merge branch 'npai/atomic-weth-depositor' into bz/atomicWethAdapters
bmzig Oct 25, 2024
3fc3767
Merge branch 'master' into bz/atomicWethAdapters
bmzig Oct 25, 2024
898bb37
deploy and configure atomic depositor
bmzig Oct 25, 2024
83cb1d6
lint
bmzig Oct 25, 2024
508f0c9
update bridge
bmzig Oct 28, 2024
2b4f3be
Merge branch 'master' into bz/atomicWethAdapters
bmzig Oct 28, 2024
8dc6384
Merge branch 'master' into bz/atomicWethAdapters
james-a-morris Dec 19, 2024
9c6949a
Merge branch 'master' into bz/atomicWethAdapters
bmzig Dec 20, 2024
331b215
improve: split netValue from bridgeValue in the atomic depositor (#1963)
bmzig Dec 23, 2024
16a21a0
reconfigure atomic depositor and update adapters
bmzig Dec 25, 2024
cb96815
update tests
bmzig Dec 25, 2024
d94db99
Merge branch 'master' into bz/atomicWethAdapters
bmzig Dec 31, 2024
905792a
revert changes to contract addresses
bmzig Dec 31, 2024
a4a4316
Merge branch 'master' into bz/atomicWethAdapters
bmzig Jan 6, 2025
ea1aa54
Merge branch 'master' into bz/atomicWethAdapters
bmzig Jan 6, 2025
eabe3c3
Merge branch 'master' into bz/atomicWethAdapters
bmzig Jan 7, 2025
2a37087
update atomic depositor to accomodate custom gas tokens
bmzig Jan 9, 2025
813dc2c
Merge branch 'master' into bz/atomicWethAdapters
bmzig Jan 9, 2025
05741d8
Merge branch 'master' into bz/atomicWethAdapters
bmzig Jan 9, 2025
00a51c1
update relayer to use new atomic depositor
bmzig Jan 9, 2025
4c82a72
Merge remote-tracking branch 'origin/bz/atomicWethAdapters' into bz/a…
bmzig Jan 9, 2025
ad4e7b3
update test with new function signature
bmzig Jan 10, 2025
03e9586
Merge branch 'master' into bz/atomicWethAdapters
bmzig Jan 10, 2025
89400b3
Merge branch 'master' into bz/atomicWethAdapters
bmzig Jan 16, 2025
8e85670
remove old deployments
bmzig Jan 16, 2025
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
197 changes: 75 additions & 122 deletions contracts/AtomicWethDepositor.sol
Original file line number Diff line number Diff line change
@@ -1,144 +1,97 @@
// 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;
}

interface OvmL1Bridge {
function depositETHTo(address _to, uint32 _l2Gas, bytes calldata _data) external payable;
}
/**
* @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;
}

interface PolygonL1Bridge {
function depositEtherFor(address _to) external payable;
}
Weth public immutable WETH = Weth(0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2);

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);
}
/**
* @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 LineaL1MessageService {
function sendMessage(address _to, uint256 _fee, bytes calldata _calldata) external payable;
}
///////////////////////////////
// Events //
///////////////////////////////

/**
* @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.
*/
event AtomicWethDepositInitiated(address indexed from, uint256 indexed chainId, uint256 amount);

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 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 == 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);
}
///////////////////////////////
// Errors //
///////////////////////////////

error InvalidBridgeFunction();

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

function bridgeWethToPolygon(address to, uint256 amount) public {
weth.transferFrom(msg.sender, address(this), amount);
weth.withdraw(amount);
polygonL1Bridge.depositEtherFor{ value: amount }(to);
/**
* @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 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);
///////////////////////////////
// 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 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);
///////////////////////////////
// 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);
}

fallback() external payable {}
Expand Down
6 changes: 3 additions & 3 deletions contracts/MockAtomicWethDepositor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@
pragma solidity ^0.8.0;

contract MockAtomicWethDepositor {
event ZkSyncEthDepositInitiated(address indexed from, address indexed to, uint256 amount);
event AtomicWethDepositInitiated(address indexed from, uint256 indexed chainId, uint256 amount);

function bridgeWethToZkSync(address to, uint256 amount, uint256, uint256, address) public {
emit ZkSyncEthDepositInitiated(msg.sender, to, amount);
function bridgeWeth(uint256 chainId, uint256 amount, bytes calldata) public {
emit AtomicWethDepositInitiated(msg.sender, chainId, amount);
}
}
12 changes: 10 additions & 2 deletions src/adapter/bridges/LineaWethBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,9 @@ export class LineaWethBridge extends BaseBridgeAdapter {
protected atomicDepositor: Contract;
protected blockFinder: BlockFinder;

// We by default do not include a fee for Linea bridges.
protected bridgeFee = 0;

constructor(
l2chainId: number,
hubChainId: number,
Expand All @@ -43,10 +46,15 @@ export class LineaWethBridge extends BaseBridgeAdapter {
l2Token: string,
amount: BigNumber
): Promise<BridgeTransactionDetails> {
const bridgeCalldata = this.getL1Bridge().interface.encodeFunctionData("sendMessage", [
toAddress,
this.bridgeFee,
"0x",
]);
return Promise.resolve({
contract: this.atomicDepositor,
method: "bridgeWethToLinea",
args: [toAddress, amount],
method: "bridgeWeth",
args: [this.l2chainId, amount, bridgeCalldata],
});
}

Expand Down
9 changes: 7 additions & 2 deletions src/adapter/bridges/OpStackWethBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,15 @@ export class OpStackWethBridge extends BaseBridgeAdapter {
l2Token: string,
amount: BigNumber
): Promise<BridgeTransactionDetails> {
const bridgeCalldata = this.getL1Bridge().interface.encodeFunctionData("depositETHTo", [
toAddress,
this.l2Gas,
"0x",
]);
return Promise.resolve({
contract: this.atomicDepositor,
method: "bridgeWethToOvm",
args: [toAddress, amount, this.l2Gas, this.l2chainId],
method: "bridgeWeth",
args: [this.l2chainId, amount, bridgeCalldata],
});
}

Expand Down
9 changes: 7 additions & 2 deletions src/adapter/bridges/PolygonWethBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -18,6 +18,7 @@ import { processEvent } from "../utils";
*/
export class PolygonWethBridge extends BaseBridgeAdapter {
protected atomicDepositor: Contract;
protected rootChainManager: Contract;

constructor(
l2chainId: number,
Expand All @@ -33,10 +34,13 @@ export class PolygonWethBridge extends BaseBridgeAdapter {
const l2TokenAddresses = getL2TokenAddresses(l1Token);
const { address: l1Address, abi: l1Abi } = CONTRACT_ADDRESSES[hubChainId].polygonWethBridge;
const { address: atomicDepositorAddress, abi: atomicDepositorAbi } = CONTRACT_ADDRESSES[hubChainId].atomicDepositor;
const { address: rootChainManagerAddress, abi: rootChainManagerAbi } =
CONTRACT_ADDRESSES[hubChainId].polygonRootChainManager;
super(l2chainId, hubChainId, l1Signer, l2SignerOrProvider, [atomicDepositorAddress]);

this.l1Bridge = new Contract(l1Address, l1Abi, l1Signer);
this.atomicDepositor = new Contract(atomicDepositorAddress, atomicDepositorAbi, l1Signer);
this.rootChainManager = new Contract(rootChainManagerAddress, rootChainManagerAbi, l1Signer);

// For Polygon, we look for mint events triggered by the L2 token, not the L2 Bridge.
const l2Abi = CONTRACT_ADDRESSES[l2chainId].withdrawableErc20.abi;
Expand All @@ -49,10 +53,11 @@ export class PolygonWethBridge extends BaseBridgeAdapter {
l2Token: string,
amount: BigNumber
): Promise<BridgeTransactionDetails> {
const bridgeCalldata = this.rootChainManager.interface.encodeFunctionData("depositEtherFor", [toAddress]);
return Promise.resolve({
contract: this.atomicDepositor,
method: "bridgeWethToPolygon",
args: [toAddress, amount.toString()],
method: "bridgeWeth",
args: [this.l2chainId, amount, bridgeCalldata],
});
}

Expand Down
28 changes: 24 additions & 4 deletions src/adapter/bridges/ZKSyncWethBridge.ts
Original file line number Diff line number Diff line change
Expand Up @@ -45,9 +45,12 @@ export class ZKSyncWethBridge extends BaseBridgeAdapter {
super(l2chainId, hubChainId, l1Signer, l2SignerOrProvider, [atomicDepositorAddress]);

const { address: l2EthAddress, abi: l2EthAbi } = CONTRACT_ADDRESSES[l2chainId].eth;
const { address: mailboxAddress, abi: mailboxAbi } = CONTRACT_ADDRESSES[hubChainId].zkSyncMailbox;

this.l2Eth = new Contract(l2EthAddress, l2EthAbi, l2SignerOrProvider);
this.l2Weth = new Contract(TOKEN_SYMBOLS_MAP.WETH.addresses[l2chainId], l2EthAbi, l2SignerOrProvider);
this.atomicDepositor = new Contract(atomicDepositorAddress, atomicDepositorAbi, l1Signer);
this.l1Bridge = new Contract(mailboxAddress, mailboxAbi, l1Signer);
}

async constructL1ToL2Txn(
Expand Down Expand Up @@ -79,10 +82,26 @@ export class ZKSyncWethBridge extends BaseBridgeAdapter {
)
: BigNumber.from(2_000_000);

const currentGasPrice = await l1Provider.getGasPrice();
const l2TransactionBaseCost = await this.getL1Bridge().l2TransactionBaseCost(
currentGasPrice,
l2GasLimit,
this.gasPerPubdataLimit
);

const bridgeCalldata = this.getL1Bridge().interface.encodeFunctionData("requestL2Transaction", [
toAddress,
amount,
"0x",
l2GasLimit,
this.gasPerPubdataLimit,
[],
toAddress,
]);
return Promise.resolve({
contract: this.atomicDepositor,
method: "bridgeWethToZkSync",
args: [toAddress, amount, l2GasLimit.toString(), this.gasPerPubdataLimit, toAddress],
method: "bridgeWeth",
args: [this.l2chainId, amount.add(l2TransactionBaseCost), bridgeCalldata],
});
}

Expand Down Expand Up @@ -119,10 +138,11 @@ export class ZKSyncWethBridge extends BaseBridgeAdapter {
} else {
events = await paginatedEventQuery(
this.atomicDepositor,
this.atomicDepositor.filters.ZkSyncEthDepositInitiated(fromAddress, toAddress),
this.atomicDepositor.filters.AtomicWethDepositInitiated(fromAddress, this.l2chainId),
eventConfig
);
processedEvents = events.map((e) => processEvent(e, "_amount", "_to", "from"));
// If we are in this branch, then the depositor is an EOA, so we can assume that from == to.
processedEvents = events.map((e) => processEvent(e, "amount", "from", "from"));
}
return {
[this.resolveL2TokenAddress(l1Token)]: processedEvents,
Expand Down
Loading