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

Sync with upstream repo #74

Open
wants to merge 19 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
19 commits
Select commit Hold shift + click to select a range
201162b
improve(relayer): Permit per-chain external listener customisation (#…
pxrl Sep 27, 2024
82e4042
improve(relayer): Permit up to 120s for SpokePoolClient readiness (#1…
pxrl Sep 27, 2024
4a39db4
refactor(relayer): Factor out common listener utils (#1841)
pxrl Sep 30, 2024
560abc0
feat(relayer): Add viem-based external SpokePool listener (#1842)
pxrl Sep 30, 2024
6b42f79
improve: swap Optimism and Base OpStackAdapters with BaseChainAdapter…
bmzig Oct 2, 2024
d3a88d5
improve(relayer): Redirect in-protocol swap message (#1848)
pxrl Oct 4, 2024
47c156f
improve(relayer): Defer deposit version computation (#1847)
pxrl Oct 4, 2024
d5bf653
fix(relayer): Fix "failed start" chain log message (#1846)
pxrl Oct 7, 2024
1af89a2
improve(spokepool): Dump relayData hash on fill logs (#1849)
pxrl Oct 7, 2024
bffa498
chore: Bump SDK (#1850)
pxrl Oct 7, 2024
a626054
feat: Support World Chain (#1855)
pxrl Oct 9, 2024
97b7ee0
feat: Support finalizing on World Chain (#1859)
pxrl Oct 10, 2024
d794b4b
fix: force maximum USDC rebalance amount over CCTP to 1M (#1860)
bmzig Oct 11, 2024
4b7a4d7
feat: Support World Chain USDC bridges (#1856)
pxrl Oct 15, 2024
dd3a69d
chore: Bump SDK (#1863)
pxrl Oct 16, 2024
bd9bde4
chore: Bump viem (#1858)
pxrl Oct 16, 2024
3de3d54
improve(unwrapWeth): load WETH addresses from constants (#1840)
nicholaspai Oct 16, 2024
119ca6b
feat: Automate withdrawing ETH from OpStack chains (#1866)
nicholaspai Oct 22, 2024
be3fd41
feat: add spoke pool balance reporting to the monitor (#1868)
bmzig Oct 22, 2024
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
3 changes: 3 additions & 0 deletions contracts/AtomicWethDepositor.sol
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ contract AtomicWethDepositor {
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);
Expand All @@ -72,6 +73,8 @@ contract AtomicWethDepositor {
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) {
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 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 == 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"]
}
}
}
}
7 changes: 4 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.14",
"@across-protocol/contracts": "^3.0.10",
"@across-protocol/sdk": "^3.2.0",
"@across-protocol/constants": "^3.1.16",
"@across-protocol/contracts": "^3.0.11",
"@across-protocol/sdk": "^3.2.7",
"@arbitrum/sdk": "^3.1.3",
"@consensys/linea-sdk": "^0.2.1",
"@defi-wonderland/smock": "^2.3.5",
Expand All @@ -39,6 +39,7 @@
"redis4": "npm:redis@^4.1.0",
"superstruct": "^1.0.3",
"ts-node": "^10.9.1",
"viem": "^2.21.18",
"winston": "^3.10.0",
"zksync-ethers": "^5.7.2"
},
Expand Down
10 changes: 9 additions & 1 deletion scripts/spokepool.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import { Contract, ethers, Signer } from "ethers";
import { LogDescription } from "@ethersproject/abi";
import { constants as sdkConsts, utils as sdkUtils } from "@across-protocol/sdk";
import { ExpandedERC20__factory as ERC20 } from "@across-protocol/contracts";
import { RelayData } from "../src/interfaces";
import {
BigNumber,
formatFeePct,
Expand Down Expand Up @@ -62,11 +63,18 @@ function printDeposit(originChainId: number, log: LogDescription): void {
function printFill(destinationChainId: number, log: LogDescription): void {
const { originChainId, outputToken } = log.args;
const eventArgs = Object.keys(log.args).filter((key) => isNaN(Number(key)));
const padLeft = eventArgs.reduce((acc, cur) => (cur.length > acc ? cur.length : acc), 0);

const relayDataHash = sdkUtils.getRelayDataHash(
Object.fromEntries(eventArgs.map((arg) => [arg, log.args[arg]])) as RelayData,
destinationChainId
);

const padLeft = [...eventArgs, "relayDataHash"].reduce((acc, cur) => (cur.length > acc ? cur.length : acc), 0);

const fields = {
tokenSymbol: resolveTokenSymbols([outputToken], destinationChainId)[0],
...Object.fromEntries(eventArgs.map((key) => [key, log.args[key]])),
relayDataHash,
};
console.log(
`Fill for ${getNetworkName(originChainId)} deposit # ${log.args.depositId}:\n` +
Expand Down
27 changes: 12 additions & 15 deletions scripts/unwrapWeth.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,13 @@
import { ethers, retrieveSignerFromCLIArgs, getProvider, WETH9, toBN, isKeyOf, getNetworkName } from "../src/utils";
import {
ethers,
retrieveSignerFromCLIArgs,
getProvider,
WETH9,
toBN,
getNetworkName,
TOKEN_SYMBOLS_MAP,
assert,
} from "../src/utils";
import { askYesNoQuestion } from "./utils";
import minimist from "minimist";

Expand All @@ -15,16 +24,6 @@ const args = minimist(process.argv.slice(2), {
// \ --wallet gckms
// \ --keys bot1

const WETH_ADDRESSES = {
1: "0xc02aaa39b223fe8d0a0e5c4f27ead9083c756cc2",
10: "0x4200000000000000000000000000000000000006",
42161: "0x82af49447d8a07e3bd95bd0d56f35241523fbab1",
137: "0x7ceb23fd6bc0add59e62ac25578270cff1b9f619",
288: "0xDeadDeAddeAddEAddeadDEaDDEAdDeaDDeAD0000",
324: "0x5AEa5775959fBC2557Cc8789bC1bf90A239D9a91",
8453: "0x4200000000000000000000000000000000000006",
};

export async function run(): Promise<void> {
if (!Object.keys(args).includes("chainId")) {
throw new Error("Define `chainId` as the chain you want to connect on");
Expand All @@ -36,10 +35,8 @@ export async function run(): Promise<void> {
const signerAddr = await baseSigner.getAddress();
const chainId = Number(args.chainId);
const connectedSigner = baseSigner.connect(await getProvider(chainId));
if (!isKeyOf(chainId, WETH_ADDRESSES)) {
throw new Error("chainId does not have a defined WETH address");
}
const token = WETH_ADDRESSES[chainId];
assert(TOKEN_SYMBOLS_MAP.WETH.addresses[chainId], "chainId does not have a defined WETH address");
const token = TOKEN_SYMBOLS_MAP.WETH.addresses[chainId];
const weth = new ethers.Contract(token, WETH9.abi, connectedSigner);
const decimals = 18;
const amountFromWei = ethers.utils.formatUnits(args.amount, decimals);
Expand Down
16 changes: 15 additions & 1 deletion scripts/utils.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
import assert from "assert";
import { Contract, ethers, utils as ethersUtils } from "ethers";
import { Contract, ethers, utils as ethersUtils, Signer } from "ethers";
import readline from "readline";
import * as contracts from "@across-protocol/contracts";
import { utils as sdkUtils } from "@across-protocol/sdk";
Expand Down Expand Up @@ -133,3 +133,17 @@ export async function getSpokePoolContract(chainId: number): Promise<Contract> {
const contract = new Contract(spokePoolAddr, contracts.SpokePool__factory.abi);
return contract;
}

/**
* @description Instantiate an Across OVM SpokePool contract instance.
* @param chainId Chain ID for the SpokePool deployment.
* @returns SpokePool contract instance.
*/
export async function getOvmSpokePoolContract(chainId: number, signer?: Signer): Promise<Contract> {
const hubChainId = resolveHubChainId(chainId);
const hubPool = await getContract(hubChainId, "HubPool");
const spokePoolAddr = (await hubPool.crossChainContracts(chainId))[1];

const contract = new Contract(spokePoolAddr, contracts.Ovm_SpokePool__factory.abi, signer);
return contract;
}
121 changes: 121 additions & 0 deletions scripts/withdrawFromOpStack.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,121 @@
// Submits a bridge from OpStack L2 to L1.
// For now, this script only supports ETH withdrawals.

import {
ethers,
retrieveSignerFromCLIArgs,
getProvider,
ERC20,
WETH9,
TOKEN_SYMBOLS_MAP,
assert,
getL1TokenInfo,
Contract,
fromWei,
blockExplorerLink,
CHAIN_IDs,
} from "../src/utils";
import { CONTRACT_ADDRESSES } from "../src/common";
import { askYesNoQuestion, getOvmSpokePoolContract } from "./utils";

import minimist from "minimist";

const cliArgs = ["amount", "chainId"];
const args = minimist(process.argv.slice(2), {
string: cliArgs,
});

// Example run:
// ts-node ./scripts/withdrawFromOpStack.ts
// \ --amount 3000000000000000000
// \ --chainId 1135
// \ --wallet gckms
// \ --keys bot1

export async function run(): Promise<void> {
assert(
cliArgs.every((cliArg) => Object.keys(args).includes(cliArg)),
`Missing cliArg, expected: ${cliArgs}`
);
const baseSigner = await retrieveSignerFromCLIArgs();
const signerAddr = await baseSigner.getAddress();
const chainId = parseInt(args.chainId);
const connectedSigner = baseSigner.connect(await getProvider(chainId));
const l2Token = TOKEN_SYMBOLS_MAP.WETH?.addresses[chainId];
assert(l2Token, `WETH not found on chain ${chainId} in TOKEN_SYMBOLS_MAP`);
const l1TokenInfo = getL1TokenInfo(l2Token, chainId);
console.log("Fetched L1 token info:", l1TokenInfo);
assert(l1TokenInfo.symbol === "ETH", "Only WETH withdrawals are supported for now.");
const amount = args.amount;
const amountFromWei = ethers.utils.formatUnits(amount, l1TokenInfo.decimals);
console.log(`Amount to bridge from chain ${chainId}: ${amountFromWei} ${l2Token}`);

const erc20 = new Contract(l2Token, ERC20.abi, connectedSigner);
const currentBalance = await erc20.balanceOf(signerAddr);
const currentEthBalance = await connectedSigner.getBalance();
console.log(
`Current WETH balance for account ${signerAddr}: ${fromWei(currentBalance, l1TokenInfo.decimals)} ${l2Token}`
);
console.log(`Current ETH balance for account ${signerAddr}: ${fromWei(currentEthBalance, l1TokenInfo.decimals)}`);

// First offer user option to unwrap WETH into ETH.
const weth = new Contract(l2Token, WETH9.abi, connectedSigner);
if (await askYesNoQuestion(`\nUnwrap ${amount} of WETH @ ${weth.address}?`)) {
const unwrap = await weth.withdraw(amount);
console.log(`Submitted transaction: ${blockExplorerLink(unwrap.hash, chainId)}.`);
const receipt = await unwrap.wait();
console.log("Unwrap complete...", receipt);
}

// Now, submit a withdrawal:
const ovmStandardBridgeObj = CONTRACT_ADDRESSES[chainId].ovmStandardBridge;
assert(CONTRACT_ADDRESSES[chainId].ovmStandardBridge, "ovmStandardBridge for chain not found in CONTRACT_ADDRESSES");
const ovmStandardBridge = new Contract(ovmStandardBridgeObj.address, ovmStandardBridgeObj.abi, connectedSigner);
const bridgeETHToArgs = [
signerAddr, // to
200_000, // minGasLimit
"0x", // extraData
{ value: amount }, // msg.value
];

console.log(
`Submitting bridgeETHTo on the OVM standard bridge @ ${ovmStandardBridge.address} with the following args: `,
...bridgeETHToArgs
);

// Sanity check that the ovmStandardBridge contract is the one we expect by comparing its stored addresses
// with the ones we have recorded.
const spokePool = await getOvmSpokePoolContract(chainId, connectedSigner);
const expectedL2Messenger = await spokePool.MESSENGER();
const l2Messenger = await ovmStandardBridge.MESSENGER();
assert(
l2Messenger === expectedL2Messenger,
`Unexpected L2 messenger address in ovmStandardBridge contract, expected: ${expectedL2Messenger}, got: ${l2Messenger}`
);
const l1StandardBridge = await ovmStandardBridge.l1TokenBridge();
const expectedL1StandardBridge = CONTRACT_ADDRESSES[CHAIN_IDs.MAINNET][`ovmStandardBridge_${chainId}`].address;
assert(
l1StandardBridge === expectedL1StandardBridge,
`Unexpected L1 standard bridge address in ovmStandardBridge contract, expected: ${expectedL1StandardBridge}, got: ${l1StandardBridge}`
);
if (!(await askYesNoQuestion("\nDo you want to proceed?"))) {
return;
}
const withdrawal = await ovmStandardBridge.bridgeETHTo(...bridgeETHToArgs);
console.log(`Submitted withdrawal: ${blockExplorerLink(withdrawal.hash, chainId)}.`);
const receipt = await withdrawal.wait();
console.log("Receipt", receipt);
}

if (require.main === module) {
run()
.then(async () => {
// eslint-disable-next-line no-process-exit
process.exit(0);
})
.catch(async (error) => {
console.error("Process exited with", error);
// eslint-disable-next-line no-process-exit
process.exit(1);
});
}
Loading