Skip to content

Commit

Permalink
Merge branch 'master' into pxrl/gasLimitWrong
Browse files Browse the repository at this point in the history
  • Loading branch information
pxrl authored Nov 4, 2024
2 parents 6bf012a + 5f37034 commit 276b191
Show file tree
Hide file tree
Showing 14 changed files with 205 additions and 162 deletions.
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
2 changes: 1 addition & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,7 @@
"dependencies": {
"@across-protocol/constants": "^3.1.16",
"@across-protocol/contracts": "^3.0.11",
"@across-protocol/sdk": "^3.2.7",
"@across-protocol/sdk": "^3.2.11",
"@arbitrum/sdk": "^3.1.3",
"@consensys/linea-sdk": "^0.2.1",
"@defi-wonderland/smock": "^2.3.5",
Expand Down
7 changes: 6 additions & 1 deletion src/adapter/BaseChainAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -208,7 +208,12 @@ export class BaseChainAdapter {
}

const value = ethBalance.sub(target);
this.log("Wrapping ETH", { threshold, target, value, ethBalance }, "debug", "wrapEthIfAboveThreshold");
this.log(
`Wrapping ETH on chain ${getNetworkName(this.chainId)}`,
{ threshold, target, value, ethBalance },
"debug",
"wrapEthIfAboveThreshold"
);
const method = "deposit";
const formatFunc = createFormatFunction(2, 4, false, 18);
const mrkdwn =
Expand Down
12 changes: 10 additions & 2 deletions src/clients/bridges/AdapterManager.ts
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import { ArbitrumAdapter, PolygonAdapter, ZKSyncAdapter, LineaAdapter, ScrollAda
import { CHAIN_IDs, TOKEN_SYMBOLS_MAP } from "@across-protocol/constants";

import { BaseChainAdapter } from "../../adapter";
import { EthereumAdapter } from "./EthereumAdapter";

export class AdapterManager {
public adapters: { [chainId: number]: BaseChainAdapter } = {};
Expand All @@ -21,7 +22,7 @@ export class AdapterManager {
// receiving ETH that needs to be wrapped on the L2. This array contains the chainIds of the chains that this
// manager will attempt to wrap ETH on into WETH. This list also includes chains like Arbitrum where the relayer is
// expected to receive ETH as a gas refund from an L1 to L2 deposit that was intended to rebalance inventory.
public chainsToWrapEtherOn = [...spokesThatHoldEthAndWeth, CHAIN_IDs.ARBITRUM];
private chainsToWrapEtherOn = [...spokesThatHoldEthAndWeth, CHAIN_IDs.ARBITRUM, CHAIN_IDs.MAINNET];

constructor(
readonly logger: winston.Logger,
Expand Down Expand Up @@ -232,7 +233,14 @@ export class AdapterManager {
wrapThreshold.gte(wrapTarget),
`wrapEtherThreshold ${wrapThreshold.toString()} must be >= wrapEtherTarget ${wrapTarget.toString()}`
);
await this.adapters[chainId].wrapEthIfAboveThreshold(wrapThreshold, wrapTarget, simMode);
if (chainId === CHAIN_IDs.MAINNET) {
// For mainnet, construct one-off adapter to wrap ETH, because Ethereum is typically not a chain
// that we have an adapter for.
const ethAdapter = new EthereumAdapter(this.logger, this.spokePoolClients);
await ethAdapter.wrapEthIfAboveThreshold(wrapThreshold, wrapTarget, simMode);
} else {
await this.adapters[chainId].wrapEthIfAboveThreshold(wrapThreshold, wrapTarget, simMode);
}
}
);
}
Expand Down
25 changes: 25 additions & 0 deletions src/clients/bridges/EthereumAdapter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
import { DEFAULT_GAS_MULTIPLIER } from "../../common";
import { CHAIN_IDs, winston } from "../../utils";
import { SpokePoolClient } from "../SpokePoolClient";
import { BaseChainAdapter } from "../../adapter/BaseChainAdapter";

// This adapter is only used by the AdapterManager to wrap ETH on Mainnet, so we don't pass in any supported
// tokens or bridges.
export class EthereumAdapter extends BaseChainAdapter {
constructor(logger: winston.Logger, readonly spokePoolClients: { [chainId: number]: SpokePoolClient }) {
const { MAINNET } = CHAIN_IDs;
const bridges = {};
const supportedTokens = [];
const monitoredAddresses = [];
super(
spokePoolClients,
MAINNET,
MAINNET,
monitoredAddresses,
logger,
supportedTokens,
bridges,
DEFAULT_GAS_MULTIPLIER[MAINNET] ?? 1
);
}
}
16 changes: 16 additions & 0 deletions src/common/Constants.ts
Original file line number Diff line number Diff line change
Expand Up @@ -540,6 +540,22 @@ export const EXPECTED_L1_TO_L2_MESSAGE_TIME = {
};

export const OPSTACK_CONTRACT_OVERRIDES = {
[CHAIN_IDs.BASE]: {
// https://github.com/ethereum-optimism/ecosystem/blob/8df6ab1afcf49312dc7e89ed079f910843d74427/packages/sdk/src/utils/chain-constants.ts#L252
l1: {
AddressManager: "0x8EfB6B5c4767B09Dc9AA6Af4eAA89F749522BaE2",
L1CrossDomainMessenger: "0x866E82a600A1414e583f7F13623F1aC5d58b0Afa",
L1StandardBridge: CONTRACT_ADDRESSES[CHAIN_IDs.MAINNET].ovmStandardBridge_8453.address,
StateCommitmentChain: ZERO_ADDRESS,
CanonicalTransactionChain: ZERO_ADDRESS,
BondManager: ZERO_ADDRESS,
OptimismPortal: "0x49048044D57e1C92A77f79988d21Fa8fAF74E97e",
L2OutputOracle: "0x56315b90c40730925ec5485cf004d835058518A0",
OptimismPortal2: "0x49048044D57e1C92A77f79988d21Fa8fAF74E97e",
DisputeGameFactory: "0x43edB88C4B80fDD2AdFF2412A7BebF9dF42cB40e",
},
l2: DEFAULT_L2_CONTRACT_ADDRESSES,
},
[CHAIN_IDs.BLAST]: {
l1: {
AddressManager: "0xE064B565Cf2A312a3e66Fe4118890583727380C0",
Expand Down
41 changes: 22 additions & 19 deletions src/dataworker/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -123,41 +123,44 @@ flowchart TD

### Validating fills

A fill must match a deposit on every shared parameter that they have in common. The matched deposit does not have to be in the same bundle as the fill. A fill contains the following [event parameter](https://github.com/across-protocol/contracts/blob/master/contracts/SpokePool.sol#L139)'s:
A fill must match a deposit on every shared parameter that they have in common. The matched deposit does not have to be in the same bundle as the fill. A fill contains the following [event parameter](https://github.com/across-protocol/contracts/blob/a663586e8619bc74cb1da2375107bd5eef0f3144/contracts/interfaces/V3SpokePoolInterface.sol#L124)'s:

```solidity
event FilledRelay(
uint256 amount,
uint256 totalFilledAmount,
uint256 fillAmount,
event FilledV3Relay(
address inputToken,
address outputToken,
uint256 inputAmount,
uint256 outputAmount,
uint256 repaymentChainId,
uint256 indexed originChainId,
uint256 destinationChainId,
int64 relayerFeePct,
int64 realizedLpFeePct,
uint32 indexed depositId,
address destinationToken,
address relayer,
address indexed depositor,
uint32 fillDeadline,
uint32 exclusivityDeadline,
address exclusiveRelayer,
address indexed relayer,
address depositor,
address recipient,
bytes message,
RelayExecutionInfo updatableRelayData
V3RelayExecutionEventInfo relayExecutionInfo
);
```

A [deposit](https://github.com/across-protocol/contracts/blob/master/contracts/SpokePool.sol#L119) contains:
A [deposit](https://github.com/across-protocol/contracts/blob/a663586e8619bc74cb1da2375107bd5eef0f3144/contracts/interfaces/V3SpokePoolInterface.sol#L99) contains:

```solidity
event FundsDeposited(
uint256 amount,
uint256 originChainId,
event V3FundsDeposited(
address inputToken,
address outputToken,
uint256 inputAmount,
uint256 outputAmount,
uint256 indexed destinationChainId,
int64 relayerFeePct,
uint32 indexed depositId,
uint32 quoteTimestamp,
address originToken,
address recipient,
uint32 fillDeadline,
uint32 exclusivityDeadline,
address indexed depositor,
address recipient,
address exclusiveRelayer,
bytes message
);
```
Expand Down
Loading

0 comments on commit 276b191

Please sign in to comment.