From 1372c1d4a34ec1d561053dbda122025ae31a73e3 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 30 Aug 2024 15:55:28 -0400 Subject: [PATCH 01/21] [WIP]: Experimentally use Viem in OpStack finalizer $$ Currently showing my WIP trying to use the Viem client to support limited finalizer functionality: - check withdrawal status - build proveWithdrawalTransaction and finalizeWithdrawal args ## Current limitations of Viem: - No easy way to convert ethers Provider to [Viem transport](https://viem.sh/docs/clients/transports/custom), meaning that RPC calls won't go through our custom RetryProvider. The Viem transport does have retry and fallback logic natively but not caching built-in. I think we'll need a larger refactor to support this migration if we wanted to use Viem more widely throughout the codebase. We can probably get away with Viem in the finalizer if we restrict its use only for the few RPC calls required to support finalizer functions (e.g. building transaction args requires querying contracts) - `getWithdrawalStatus` only supports querying a transaction with a single withdrawal. This isn't very useful for us today since we usually multicall our withdrawals. - [Viem accounts](https://viem.sh/docs/clients/wallet#json-rpc-accounts) don't seem to be obviously interoperable with ethers Signers ## Suggested roadmap - Use Viem for optimism mainnet, and then a few op stack chains over time. - Build generalized Viem modules that could help us install viem into the broader codebase like: - converting between Ethers providers/signers and Viem transports/accounts --- package.json | 1 + src/finalizer/utils/opStack.ts | 77 ++++++++++++++++++++++++++++ yarn.lock | 91 ++++++++++++++++++++++++++++++++++ 3 files changed, 169 insertions(+) diff --git a/package.json b/package.json index 8e9062941..301ce9794 100644 --- a/package.json +++ b/package.json @@ -39,6 +39,7 @@ "redis4": "npm:redis@^4.1.0", "superstruct": "^1.0.3", "ts-node": "^10.9.1", + "viem": "^2.21.0", "winston": "^3.10.0", "zksync-ethers": "^5.7.2" }, diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index f6a1f44b4..cc039f117 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -1,6 +1,9 @@ import assert from "assert"; import { groupBy } from "lodash"; import * as optimismSDK from "@eth-optimism/sdk"; +import * as viem from "viem"; +import * as viemChains from "viem/chains"; +import { publicActionsL1, publicActionsL2, getWithdrawals } from "viem/op-stack"; import { HubPoolClient, SpokePoolClient } from "../../clients"; import { TokensBridged } from "../../interfaces"; import { @@ -42,6 +45,18 @@ const OP_STACK_CHAINS = Object.values(CHAIN_IDs).filter((chainId) => chainIsOPSt * (typeof OP_STACK_CHAINS)[number] then takes all elements in this array and "unions" their type (i.e. 10 | 8453 | 3443 | ... ). * https://www.typescriptlang.org/docs/handbook/release-notes/typescript-2-1.html#keyof-and-lookup-types */ + +// We might want to export this mapping of chain ID to viem chain object out of a constant +// file once we start using Viem elsewhere in the repo: +const VIEM_OP_STACK_CHAINS = { + [CHAIN_IDs.OPTIMISM]: viemChains.optimism, + // [CHAIN_IDs.BASE]: viemChains.base, + // [CHAIN_IDs.BLAST]: viemChains.blast, + // [CHAIN_IDs.REDSTONE]: viemChains.redstone, + // [CHAIN_IDs.LISK]: viemChains.lisk, + [CHAIN_IDs.OPTIMISM_SEPOLIA]: viemChains.optimismSepolia, +}; + type OVM_CHAIN_ID = (typeof OP_STACK_CHAINS)[number]; type OVM_CROSS_CHAIN_MESSENGER = optimismSDK.CrossChainMessenger; @@ -94,6 +109,68 @@ export async function opStackFinalizer( latestBlockToProve, }); + // @dev Experimental: try using Viem if its available for this chain. Eventually we should + // fully migrate from SDK to Viem. Note, the Viem "provider" is not easily translateable from the ethers.js provider, + // so any RPC requests sent from the Viem client will likely not inherit benefits of our custom RetryProvider such + // as quorum, caching, fallbacks, etc. This is workable for now if we isolate viem usage. + if (VIEM_OP_STACK_CHAINS[chainId]) { + const hubChainId = chainIsProd(chainId) ? CHAIN_IDs.MAINNET : CHAIN_IDs.SEPOLIA; + const publicClientL1 = viem + .createPublicClient({ + chain: chainIsProd(chainId) ? viemChains.mainnet : viemChains.sepolia, + transport: viem.http(getCachedProvider(hubChainId, true).providers[0].connection.url), + }) + .extend(publicActionsL1() as any) as any; + const publicClientL2 = viem + .createPublicClient({ + chain: VIEM_OP_STACK_CHAINS[chainId], + transport: viem.http(getCachedProvider(chainId, true).providers[0].connection.url), + }) + .extend(publicActionsL2() as any) as any; + const uniqueTokenhashes = {}; + const logIndexesForMessage = []; + const events = spokePoolClient + .getTokensBridged() + .filter((e) => !compareAddressesSimple(e.l2TokenAddress, TOKEN_SYMBOLS_MAP["USDC"].addresses[chainId])); + for (const event of events) { + uniqueTokenhashes[event.transactionHash] = uniqueTokenhashes[event.transactionHash] ?? 0; + const logIndex = uniqueTokenhashes[event.transactionHash]; + logIndexesForMessage.push(logIndex); + uniqueTokenhashes[event.transactionHash] += 1; + } + + events.map(async (event, i) => { + const receipt = await (publicClientL2 as viem.PublicClient).getTransactionReceipt({ + hash: event.transactionHash as `0x${string}`, + }); + const withdrawal = getWithdrawals(receipt)[logIndexesForMessage[i]]; + if (logIndexesForMessage[i] !== 0) { + console.warn( + "Multiple events in the same transaction are not supported by Viem yet, cannot finalize withdrawal", + withdrawal + ); + return; + } + const withdrawalStatus = await publicClientL1.getWithdrawalStatus({ + receipt, + targetChain: VIEM_OP_STACK_CHAINS[chainId], + }); + if (withdrawalStatus === "ready-to-prove") { + const l2Output = await publicClientL1.getL2Output({ + // [!code hl] + l2BlockNumber: event.blockNumber, // [!code hl] + targetChain: VIEM_OP_STACK_CHAINS[chainId], // [!code hl] + }); // + const { l2OutputIndex, outputRootProof, withdrawalProof } = await (publicClientL2 as any).buildProveWithdrawal({ + withdrawal, + output: l2Output, + }); + const proofArgs = [withdrawal, l2OutputIndex, outputRootProof, withdrawalProof]; + console.log(`Withdrawal ${event.transactionHash} is ready to prove: `, proofArgs); + } + }); + } + const proofs = await multicallOptimismL1Proofs( chainId, recentTokensBridgedEvents, diff --git a/yarn.lock b/yarn.lock index 428c974d4..e606b90e8 100644 --- a/yarn.lock +++ b/yarn.lock @@ -73,6 +73,11 @@ superstruct "^0.15.4" tslib "^2.6.2" +"@adraffy/ens-normalize@1.10.0": + version "1.10.0" + resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.10.0.tgz#d2a39395c587e092d77cbbc80acf956a54f38bf7" + integrity sha512-nA9XHtlAkYfJxY7bce8DcN7eKxWWCWkU+1GR9d+U6MbNpfwQp8TI7vqOsBsMcHoT4mBu2kypKoSKnghEzOOq5Q== + "@arbitrum/sdk@^3.1.3": version "3.1.3" resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.1.3.tgz#75236043717a450b569faaa087687c51d525b0c3" @@ -1502,11 +1507,37 @@ resolved "https://registry.yarnpkg.com/@multiformats/base-x/-/base-x-4.0.1.tgz#95ff0fa58711789d53aefb2590a8b7a4e715d121" integrity sha512-eMk0b9ReBbV23xXU693TAIrLyeO5iTgBZGSJfpqriG8UkYvr/hC9u9pyMlAakDNHWmbhMZCDs6KQO0jzKD8OTw== +"@noble/curves@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.0.tgz#f05771ef64da724997f69ee1261b2417a49522d6" + integrity sha512-p+4cb332SFCrReJkCYe8Xzm0OWi4Jji5jVdIZRL/PmacmDkFNw6MrrV+gGpiPxLHbV+zKFRywUWbaseT+tZRXg== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/curves@^1.4.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.5.0.tgz#7a9b9b507065d516e6dce275a1e31db8d2a100dd" + integrity sha512-J5EKamIHnKPyClwVrzmaf5wSdQXgdHcPZIZLu3bwnbeCx8/7NPK5q2ZBWF+5FvYGByjiQQsJYX6jfgB2wDPn3A== + dependencies: + "@noble/hashes" "1.4.0" + +"@noble/curves@~1.4.0": + version "1.4.2" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.4.2.tgz#40309198c76ed71bc6dbf7ba24e81ceb4d0d1fe9" + integrity sha512-TavHr8qycMChk8UwMld0ZDRvatedkzWfH8IiaeGCfymOP5i0hSCozz9vHOL0nkwk7HRMlFnAiKpS2jrUmSybcw== + dependencies: + "@noble/hashes" "1.4.0" + "@noble/hashes@1.0.0", "@noble/hashes@~1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae" integrity sha512-DZVbtY62kc3kkBtMHqwCOfXrT/hnoORy5BJ4+HU1IR59X0KWAOqsfzQPcUl/lQLlG7qXbe/fZ3r/emxtAl+sqg== +"@noble/hashes@1.4.0", "@noble/hashes@^1.4.0", "@noble/hashes@~1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.4.0.tgz#45814aa329f30e4fe0ba49426f49dfccdd066426" + integrity sha512-V1JJ1WTRUqHHrOSh597hURcMqVKVGL/ea3kv0gSnEdsEZ0/+VyPghM1lMNGc00z7CIQorSvbKpuJkxvuHbvdbg== + "@noble/secp256k1@1.5.5", "@noble/secp256k1@~1.5.2": version "1.5.5" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.5.5.tgz#315ab5745509d1a8c8e90d0bdf59823ccf9bcfc3" @@ -2372,6 +2403,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.0.0.tgz#109fb595021de285f05a7db6806f2f48296fcee7" integrity sha512-gIVaYhUsy+9s58m/ETjSJVKHhKTBMmcRb9cEV5/5dwvfDlfORjKrFsDeDHWRrm6RjcPvCLZFwGJjAjLj1gg4HA== +"@scure/base@~1.1.6": + version "1.1.7" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.7.tgz#fe973311a5c6267846aa131bc72e96c5d40d2b30" + integrity sha512-PPNYBslrLNNUQ/Yad37MHYsNQtK67EhWb6WtSvNLLPo7SdVZgkUjD6Dg+5On7zNwmskf8OX7I7Nx5oN+MIWE0g== + "@scure/bip32@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.0.1.tgz#1409bdf9f07f0aec99006bb0d5827693418d3aa5" @@ -2381,6 +2417,15 @@ "@noble/secp256k1" "~1.5.2" "@scure/base" "~1.0.0" +"@scure/bip32@1.4.0": + version "1.4.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.4.0.tgz#4e1f1e196abedcef395b33b9674a042524e20d67" + integrity sha512-sVUpc0Vq3tXCkDGYVWGIZTRfnvu8LoTDaev7vbwh0omSvVORONr960MQWdKqJDCReIEmTj3PAr73O3aoxz7OPg== + dependencies: + "@noble/curves" "~1.4.0" + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@scure/bip39@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.0.0.tgz#47504e58de9a56a4bbed95159d2d6829fa491bb0" @@ -2389,6 +2434,14 @@ "@noble/hashes" "~1.0.0" "@scure/base" "~1.0.0" +"@scure/bip39@1.3.0": + version "1.3.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.3.0.tgz#0f258c16823ddd00739461ac31398b4e7d6a18c3" + integrity sha512-disdg7gHuTDZtY+ZdkmLpPCk7fxZSu3gBiEGuoC1XYxv9cGx3Z6cpTggCgW6odSOOIXCiDjuGejW+aJKCY/pIQ== + dependencies: + "@noble/hashes" "~1.4.0" + "@scure/base" "~1.1.6" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -3425,6 +3478,11 @@ abbrev@1.0.x: web3-eth-abi "^1.2.1" web3-utils "^1.2.1" +abitype@1.0.5: + version "1.0.5" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.5.tgz#29d0daa3eea867ca90f7e4123144c1d1270774b6" + integrity sha512-YzDhti7cjlfaBhHutMaboYB21Ha3rXR9QTkNJFzYC4kC8YclaiwPBBBJY8ejFdu2wnJeZCVZSMlQJ7fi8S6hsw== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -9001,6 +9059,11 @@ isomorphic-unfetch@^3.0.0: node-fetch "^2.6.1" unfetch "^4.2.0" +isows@1.0.4: + version "1.0.4" + resolved "https://registry.yarnpkg.com/isows/-/isows-1.0.4.tgz#810cd0d90cc4995c26395d2aa4cfa4037ebdf061" + integrity sha512-hEzjY+x9u9hPmBom9IIAqdJCwNLax+xrPb51vEPpERoFlIxgmZcHzsT5jKG06nvInKOBGvReAVz80Umed5CczQ== + isstream@~0.1.2: version "0.1.2" resolved "https://registry.yarnpkg.com/isstream/-/isstream-0.1.2.tgz#47e63f7af55afa6f92e1500e690eb8b8529c099a" @@ -14576,6 +14639,21 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +viem@^2.21.0: + version "2.21.0" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.0.tgz#715ad561853fba50d6ed48ebcaee007b5f9bf7ff" + integrity sha512-9g3Gw2nOU6t4bNuoDI5vwVExzIxseU0J7Jjx10gA2RNQVrytIrLxggW++tWEe3w4mnnm/pS1WgZFjQ/QKf/nHw== + dependencies: + "@adraffy/ens-normalize" "1.10.0" + "@noble/curves" "1.4.0" + "@noble/hashes" "1.4.0" + "@scure/bip32" "1.4.0" + "@scure/bip39" "1.3.0" + abitype "1.0.5" + isows "1.0.4" + webauthn-p256 "0.0.5" + ws "8.17.1" + walk-up-path@^1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/walk-up-path/-/walk-up-path-1.0.0.tgz#d4745e893dd5fd0dbb58dd0a4c6a33d9c9fec53e" @@ -15068,6 +15146,14 @@ web3@1.8.2, web3@^1.6.0: web3-shh "1.8.2" web3-utils "1.8.2" +webauthn-p256@0.0.5: + version "0.0.5" + resolved "https://registry.yarnpkg.com/webauthn-p256/-/webauthn-p256-0.0.5.tgz#0baebd2ba8a414b21cc09c0d40f9dd0be96a06bd" + integrity sha512-drMGNWKdaixZNobeORVIqq7k5DsRC9FnG201K2QjeOoQLmtSDaSsVZdkg6n5jUALJKcAG++zBPJXmv6hy0nWFg== + dependencies: + "@noble/curves" "^1.4.0" + "@noble/hashes" "^1.4.0" + webidl-conversions@^3.0.0: version "3.0.1" resolved "https://registry.yarnpkg.com/webidl-conversions/-/webidl-conversions-3.0.1.tgz#24534275e2a7bc6be7bc86611cc16ae0a5654871" @@ -15299,6 +15385,11 @@ ws@7.4.6: resolved "https://registry.yarnpkg.com/ws/-/ws-7.4.6.tgz#5654ca8ecdeee47c33a9a4bf6d28e2be2980377c" integrity sha512-YmhHDO4MzaDLB+M9ym/mDA5z0naX8j7SIlT8f8z+I0VtzsRbekxEutHSme7NPS2qE8StCYQNUnfWdXta/Yu85A== +ws@8.17.1: + version "8.17.1" + resolved "https://registry.yarnpkg.com/ws/-/ws-8.17.1.tgz#9293da530bb548febc95371d90f9c878727d919b" + integrity sha512-6XQFvXTkbfUOZOKKILFG1PDK2NDQs4azKQl26T0YS5CxqWLgXajbPZ+h4gZekJyRqFU8pvnbAbbs/3TgRPy+GQ== + ws@^3.0.0: version "3.3.3" resolved "https://registry.yarnpkg.com/ws/-/ws-3.3.3.tgz#f1cf84fe2d5e901ebce94efaece785f187a228f2" From 49ce5527cd6e42ea6c34a9f3a58ed3c16ef8bed5 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 6 Sep 2024 09:36:04 -0400 Subject: [PATCH 02/21] Add timeToFinalize estimate --- src/finalizer/utils/opStack.ts | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index cc039f117..3ee1ebe35 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -167,6 +167,12 @@ export async function opStackFinalizer( }); const proofArgs = [withdrawal, l2OutputIndex, outputRootProof, withdrawalProof]; console.log(`Withdrawal ${event.transactionHash} is ready to prove: `, proofArgs); + } else if (withdrawalStatus === "waiting-to-finalize") { + const { seconds } = await publicClientL1.getTimeToFinalize({ + withdrawalHash: withdrawal.withdrawalHash, + targetChain: VIEM_OP_STACK_CHAINS[chainId], + }); + console.log(`Withdrawal ${event.transactionHash} in in challenge period for ${seconds / 60 / 60} hours`); } }); } From 75bb38965510829eba5ed65eb0535a7825011a29 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 6 Sep 2024 15:44:27 -0400 Subject: [PATCH 03/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 3ee1ebe35..d9c3e5947 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -173,6 +173,8 @@ export async function opStackFinalizer( targetChain: VIEM_OP_STACK_CHAINS[chainId], }); console.log(`Withdrawal ${event.transactionHash} in in challenge period for ${seconds / 60 / 60} hours`); + } else if (withdrawalStatus === "ready-to-finalize"){ + console.log(`Withdrawal ${event.transactionHash} is ready to finalize with args: `, withdrawal); } }); } From 594fd2453d224a20069e4e87f47c08f40511e84a Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Thu, 26 Sep 2024 12:12:42 -0400 Subject: [PATCH 04/21] Add calldata construction using viem --- src/common/abi/OpStackPortalL1.json | 129 ++++++++++++++++++++++++++++ src/finalizer/utils/opStack.ts | 14 ++- 2 files changed, 140 insertions(+), 3 deletions(-) create mode 100644 src/common/abi/OpStackPortalL1.json diff --git a/src/common/abi/OpStackPortalL1.json b/src/common/abi/OpStackPortalL1.json new file mode 100644 index 000000000..d3e912594 --- /dev/null +++ b/src/common/abi/OpStackPortalL1.json @@ -0,0 +1,129 @@ +[ + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + } + ], + "name": "finalizeWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_disputeGameIndex", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "version", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "messagePasserStorageRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "latestBlockhash", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootProof", + "name": "_outputRootProof", + "type": "tuple" + }, + { + "internalType": "bytes[]", + "name": "_withdrawalProof", + "type": "bytes[]" + } + ], + "name": "proveWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] \ No newline at end of file diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index d9c3e5947..c5094d70e 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -27,6 +27,7 @@ import { ethers, } from "../../utils"; import { CONTRACT_ADDRESSES, Multicall2Call, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; +import OPStackPortalL1 from "../../common/abi/OpStackPortalL1.json"; import { FinalizerPromise, CrossChainMessage } from "../types"; const { utils } = ethers; @@ -139,6 +140,11 @@ export async function opStackFinalizer( uniqueTokenhashes[event.transactionHash] += 1; } + const crossChainMessenger = new Contract( + VIEM_OP_STACK_CHAINS[chainId].contracts.portal[hubChainId].address, + OPStackPortalL1 + ); + events.map(async (event, i) => { const receipt = await (publicClientL2 as viem.PublicClient).getTransactionReceipt({ hash: event.transactionHash as `0x${string}`, @@ -166,15 +172,17 @@ export async function opStackFinalizer( output: l2Output, }); const proofArgs = [withdrawal, l2OutputIndex, outputRootProof, withdrawalProof]; - console.log(`Withdrawal ${event.transactionHash} is ready to prove: `, proofArgs); + const callData = await crossChainMessenger.populateTransaction.proveWithdrawalTransaction(...proofArgs); + console.log(`Withdrawal ${event.transactionHash} is ready to prove: `, proofArgs, callData); } else if (withdrawalStatus === "waiting-to-finalize") { const { seconds } = await publicClientL1.getTimeToFinalize({ withdrawalHash: withdrawal.withdrawalHash, targetChain: VIEM_OP_STACK_CHAINS[chainId], }); console.log(`Withdrawal ${event.transactionHash} in in challenge period for ${seconds / 60 / 60} hours`); - } else if (withdrawalStatus === "ready-to-finalize"){ - console.log(`Withdrawal ${event.transactionHash} is ready to finalize with args: `, withdrawal); + } else if (withdrawalStatus === "ready-to-finalize") { + const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); + console.log(`Withdrawal ${event.transactionHash} is ready to finalize with args: `, withdrawal, callData); } }); } From 0b76755a4e3c69b1b02a2d6cd181a9b6ecae4dc4 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 15 Oct 2024 15:32:52 -0400 Subject: [PATCH 05/21] Add more helpers --- src/finalizer/utils/opStack.ts | 59 ++++++++++++++++++++++++++++++---- 1 file changed, 53 insertions(+), 6 deletions(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index c5094d70e..b08fe44af 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -85,7 +85,7 @@ export async function opStackFinalizer( // - Don't submit proofs for finalizations older than 1 day // - Don't try to withdraw tokens that are not past the 7 day challenge period const redis = await getRedisCache(logger); - const minimumFinalizationTime = getCurrentTime() - 7 * 3600 * 24; + const minimumFinalizationTime = getCurrentTime() - 32 * 3600 * 24; const latestBlockToProve = await getBlockForTimestamp(chainId, minimumFinalizationTime, undefined, redis); const { recentTokensBridgedEvents = [], olderTokensBridgedEvents = [] } = groupBy( spokePoolClient.getTokensBridged().filter( @@ -114,6 +114,13 @@ export async function opStackFinalizer( // fully migrate from SDK to Viem. Note, the Viem "provider" is not easily translateable from the ethers.js provider, // so any RPC requests sent from the Viem client will likely not inherit benefits of our custom RetryProvider such // as quorum, caching, fallbacks, etc. This is workable for now if we isolate viem usage. + const viemTxns: { + callData: Multicall2Call[]; + withdrawals: CrossChainMessage[]; + } = { + callData: [], + withdrawals: [], + }; if (VIEM_OP_STACK_CHAINS[chainId]) { const hubChainId = chainIsProd(chainId) ? CHAIN_IDs.MAINNET : CHAIN_IDs.SEPOLIA; const publicClientL1 = viem @@ -132,7 +139,11 @@ export async function opStackFinalizer( const logIndexesForMessage = []; const events = spokePoolClient .getTokensBridged() - .filter((e) => !compareAddressesSimple(e.l2TokenAddress, TOKEN_SYMBOLS_MAP["USDC"].addresses[chainId])); + .filter( + (e) => + !compareAddressesSimple(e.l2TokenAddress, TOKEN_SYMBOLS_MAP["USDC"].addresses[chainId]) && + e.blockNumber <= latestBlockToProve + ); for (const event of events) { uniqueTokenhashes[event.transactionHash] = uniqueTokenhashes[event.transactionHash] ?? 0; const logIndex = uniqueTokenhashes[event.transactionHash]; @@ -142,18 +153,26 @@ export async function opStackFinalizer( const crossChainMessenger = new Contract( VIEM_OP_STACK_CHAINS[chainId].contracts.portal[hubChainId].address, - OPStackPortalL1 + OPStackPortalL1, + signer ); events.map(async (event, i) => { + // Useful information for event: + const l1TokenInfo = getL1TokenInfo(event.l2TokenAddress, chainId); + const amountFromWei = convertFromWei(event.amountToReturn.toString(), l1TokenInfo.decimals); + const receipt = await (publicClientL2 as viem.PublicClient).getTransactionReceipt({ hash: event.transactionHash as `0x${string}`, }); const withdrawal = getWithdrawals(receipt)[logIndexesForMessage[i]]; + // Note: to fix this, you need to hardcode a change to the LOC here which assumes there + // is one MessagePassed event per log + // - https://github.com/wevm/viem/blob/df32667fd1038dbcb7bedec67381e8a2ff468a4e/src/op-stack/actions/getWithdrawalStatus.ts#L135 if (logIndexesForMessage[i] !== 0) { console.warn( "Multiple events in the same transaction are not supported by Viem yet, cannot finalize withdrawal", - withdrawal + { withdrawal, receipt: event.transactionHash } ); return; } @@ -174,15 +193,43 @@ export async function opStackFinalizer( const proofArgs = [withdrawal, l2OutputIndex, outputRootProof, withdrawalProof]; const callData = await crossChainMessenger.populateTransaction.proveWithdrawalTransaction(...proofArgs); console.log(`Withdrawal ${event.transactionHash} is ready to prove: `, proofArgs, callData); + viemTxns.callData.push({ + callData: callData as any, + target: crossChainMessenger.address, + }); + viemTxns.withdrawals.push({ + originationChainId: chainId, + l1TokenSymbol: l1TokenInfo.symbol, + amount: amountFromWei, + type: "misc", + miscReason: "proof", + destinationChainId: hubPoolClient.chainId, + }); + // const proveTxn = await(await crossChainMessenger.proveWithdrawalTransaction(...proofArgs)).wait(); + // console.log(`Submitted proof`, proveTxn); } else if (withdrawalStatus === "waiting-to-finalize") { const { seconds } = await publicClientL1.getTimeToFinalize({ withdrawalHash: withdrawal.withdrawalHash, targetChain: VIEM_OP_STACK_CHAINS[chainId], }); + // console.log(`Withdrawal hash: ${event.transactionHash} for withdrawal of ${event.amountToReturn.toString()} of ${event.l2TokenAddress}`); console.log(`Withdrawal ${event.transactionHash} in in challenge period for ${seconds / 60 / 60} hours`); } else if (withdrawalStatus === "ready-to-finalize") { const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); console.log(`Withdrawal ${event.transactionHash} is ready to finalize with args: `, withdrawal, callData); + viemTxns.callData.push({ + callData: callData as any, + target: crossChainMessenger.address, + }); + viemTxns.withdrawals.push({ + originationChainId: chainId, + l1TokenSymbol: l1TokenInfo.symbol, + amount: amountFromWei, + type: "withdrawal", + destinationChainId: hubPoolClient.chainId, + }); + // const finalizeTxn = await(await crossChainMessenger.finalizeWithdrawalTransaction(withdrawal)).wait(); + // console.log(`Executed finalization`, finalizeTxn); } }); } @@ -211,8 +258,8 @@ export async function opStackFinalizer( logger ); - const callData = [...proofs.callData, ...finalizations.callData]; - const crossChainTransfers = [...proofs.withdrawals, ...finalizations.withdrawals]; + const callData = viemTxns.callData; + const crossChainTransfers = viemTxns.withdrawals; return { callData, crossChainMessages: crossChainTransfers }; } From a53188d3730a19539ccd5504fb55883c8d148c9c Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 21 Oct 2024 10:48:13 -0400 Subject: [PATCH 06/21] Use new viem version that allows logIndex to be passed to getWithdrawalStatus --- package.json | 2 +- src/finalizer/utils/opStack.ts | 17 ++++++--------- yarn.lock | 39 ++++++++++++++++++++++++++++------ 3 files changed, 39 insertions(+), 19 deletions(-) diff --git a/package.json b/package.json index 6d074695c..daae46464 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "redis4": "npm:redis@^4.1.0", "superstruct": "^1.0.3", "ts-node": "^10.9.1", - "viem": "^2.21.18", + "viem": "^2.21.30", "winston": "^3.10.0", "zksync-ethers": "^5.7.2" }, diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index b08fe44af..0fb04cc0a 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -166,19 +166,10 @@ export async function opStackFinalizer( hash: event.transactionHash as `0x${string}`, }); const withdrawal = getWithdrawals(receipt)[logIndexesForMessage[i]]; - // Note: to fix this, you need to hardcode a change to the LOC here which assumes there - // is one MessagePassed event per log - // - https://github.com/wevm/viem/blob/df32667fd1038dbcb7bedec67381e8a2ff468a4e/src/op-stack/actions/getWithdrawalStatus.ts#L135 - if (logIndexesForMessage[i] !== 0) { - console.warn( - "Multiple events in the same transaction are not supported by Viem yet, cannot finalize withdrawal", - { withdrawal, receipt: event.transactionHash } - ); - return; - } const withdrawalStatus = await publicClientL1.getWithdrawalStatus({ receipt, targetChain: VIEM_OP_STACK_CHAINS[chainId], + logIndex: logIndexesForMessage[i], }); if (withdrawalStatus === "ready-to-prove") { const l2Output = await publicClientL1.getL2Output({ @@ -213,7 +204,11 @@ export async function opStackFinalizer( targetChain: VIEM_OP_STACK_CHAINS[chainId], }); // console.log(`Withdrawal hash: ${event.transactionHash} for withdrawal of ${event.amountToReturn.toString()} of ${event.l2TokenAddress}`); - console.log(`Withdrawal ${event.transactionHash} in in challenge period for ${seconds / 60 / 60} hours`); + console.log( + `Withdrawal ${event.transactionHash} for ${amountFromWei} of ${ + l1TokenInfo.symbol + } is in challenge period for ${seconds / 60 / 60} hours` + ); } else if (withdrawalStatus === "ready-to-finalize") { const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); console.log(`Withdrawal ${event.transactionHash} is ready to finalize with args: `, withdrawal, callData); diff --git a/yarn.lock b/yarn.lock index effb7bb56..5e54c4a88 100644 --- a/yarn.lock +++ b/yarn.lock @@ -13668,7 +13668,7 @@ string-format@^2.0.0: resolved "https://registry.yarnpkg.com/string-format/-/string-format-2.0.0.tgz#f2df2e7097440d3b65de31b6d40d54c96eaffb9b" integrity sha512-bbEs3scLeYNXLecRRuk6uJxdXUSj6le/8rNPHChIJTn2V79aXVTR1EH2OH5zLKKoz0V02fOUKZZcw01pLUShZA== -"string-width-cjs@npm:string-width@^4.2.0", "string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: +"string-width-cjs@npm:string-width@^4.2.0": version "4.2.3" resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== @@ -13694,6 +13694,15 @@ string-width@^1.0.1: is-fullwidth-code-point "^2.0.0" strip-ansi "^4.0.0" +"string-width@^1.0.2 || 2 || 3 || 4", string-width@^4.1.0, string-width@^4.2.0, string-width@^4.2.3: + version "4.2.3" + resolved "https://registry.yarnpkg.com/string-width/-/string-width-4.2.3.tgz#269c7117d27b05ad2e536830a8ec895ef9c6d010" + integrity sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g== + dependencies: + emoji-regex "^8.0.0" + is-fullwidth-code-point "^3.0.0" + strip-ansi "^6.0.1" + string-width@^3.0.0, string-width@^3.1.0: version "3.1.0" resolved "https://registry.yarnpkg.com/string-width/-/string-width-3.1.0.tgz#22767be21b62af1081574306f69ac51b62203961" @@ -13763,7 +13772,7 @@ stringify-package@^1.0.1: resolved "https://registry.yarnpkg.com/stringify-package/-/stringify-package-1.0.1.tgz#e5aa3643e7f74d0f28628b72f3dad5cecfc3ba85" integrity sha512-sa4DUQsYciMP1xhKWGuFM04fB0LG/9DlluZoSVywUMRNvzid6XucHK0/90xGxRoHrAaROrcHK1aPKaijCtSrhg== -"strip-ansi-cjs@npm:strip-ansi@^6.0.1", strip-ansi@^6.0.0, strip-ansi@^6.0.1: +"strip-ansi-cjs@npm:strip-ansi@^6.0.1": version "6.0.1" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== @@ -13791,6 +13800,13 @@ strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: dependencies: ansi-regex "^4.1.0" +strip-ansi@^6.0.0, strip-ansi@^6.0.1: + version "6.0.1" + resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-6.0.1.tgz#9e26c63d30f53443e9489495b2105d37b67a85d9" + integrity sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A== + dependencies: + ansi-regex "^5.0.1" + strip-ansi@^7.0.1: version "7.1.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-7.1.0.tgz#d5b6568ca689d8561370b0707685d22434faff45" @@ -14691,10 +14707,10 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -viem@^2.21.18: - version "2.21.21" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.21.tgz#11a5001fa18c8a47548a4b20ae9ddd8cfb14de3f" - integrity sha512-KJPqpAXy8kyZQICx1nURUXqd8aABP9RweAZhfp27MzMPsAAxP450cWPlEffEAUrvsyyj5edVbIcHESE8DYVzFA== +viem@^2.21.30: + version "2.21.32" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.32.tgz#b7f43b2004967036f83500260290cee45189f62a" + integrity sha512-2oXt5JNIb683oy7C8wuIJ/SeL3XtHVMEQpy1U2TA6WMnJQ4ScssRvyPwYLcaP6mKlrGXE/cR/V7ncWpvLUVPYQ== dependencies: "@adraffy/ens-normalize" "1.11.0" "@noble/curves" "1.6.0" @@ -15373,7 +15389,7 @@ workerpool@6.2.1: resolved "https://registry.yarnpkg.com/workerpool/-/workerpool-6.2.1.tgz#46fc150c17d826b86a008e5a4508656777e9c343" integrity sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw== -"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0", wrap-ansi@^7.0.0: +"wrap-ansi-cjs@npm:wrap-ansi@^7.0.0": version "7.0.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== @@ -15399,6 +15415,15 @@ wrap-ansi@^5.1.0: string-width "^3.0.0" strip-ansi "^5.0.0" +wrap-ansi@^7.0.0: + version "7.0.0" + resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-7.0.0.tgz#67e145cff510a6a6984bdf1152911d69d2eb9e43" + integrity sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q== + dependencies: + ansi-styles "^4.0.0" + string-width "^4.1.0" + strip-ansi "^6.0.0" + wrap-ansi@^8.1.0: version "8.1.0" resolved "https://registry.yarnpkg.com/wrap-ansi/-/wrap-ansi-8.1.0.tgz#56dc22368ee570face1b49819975d9b9a5ead214" From 27d6582f2a8c6dbfcc20695e9454a27b438c003f Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 22 Oct 2024 12:51:56 -0400 Subject: [PATCH 07/21] Refactor --- src/finalizer/utils/opStack.ts | 80 +++++++++++++++++++--------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 0fb04cc0a..b218b859f 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -3,7 +3,7 @@ import { groupBy } from "lodash"; import * as optimismSDK from "@eth-optimism/sdk"; import * as viem from "viem"; import * as viemChains from "viem/chains"; -import { publicActionsL1, publicActionsL2, getWithdrawals } from "viem/op-stack"; +import { publicActionsL1, publicActionsL2, getWithdrawals, GetWithdrawalStatusReturnType } from "viem/op-stack"; import { HubPoolClient, SpokePoolClient } from "../../clients"; import { TokensBridged } from "../../interfaces"; import { @@ -25,6 +25,7 @@ import { chainIsProd, Contract, ethers, + mapAsync, } from "../../utils"; import { CONTRACT_ADDRESSES, Multicall2Call, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; import OPStackPortalL1 from "../../common/abi/OpStackPortalL1.json"; @@ -110,6 +111,9 @@ export async function opStackFinalizer( latestBlockToProve, }); + let callData: Multicall2Call[]; + let crossChainTransfers: CrossChainMessage[]; + // @dev Experimental: try using Viem if its available for this chain. Eventually we should // fully migrate from SDK to Viem. Note, the Viem "provider" is not easily translateable from the ethers.js provider, // so any RPC requests sent from the Viem client will likely not inherit benefits of our custom RetryProvider such @@ -137,13 +141,9 @@ export async function opStackFinalizer( .extend(publicActionsL2() as any) as any; const uniqueTokenhashes = {}; const logIndexesForMessage = []; - const events = spokePoolClient - .getTokensBridged() - .filter( - (e) => - !compareAddressesSimple(e.l2TokenAddress, TOKEN_SYMBOLS_MAP["USDC"].addresses[chainId]) && - e.blockNumber <= latestBlockToProve - ); + const events = recentTokensBridgedEvents + .concat(olderTokensBridgedEvents) + .filter((e) => e.blockNumber <= latestBlockToProve); for (const event of events) { uniqueTokenhashes[event.transactionHash] = uniqueTokenhashes[event.transactionHash] ?? 0; const logIndex = uniqueTokenhashes[event.transactionHash]; @@ -157,7 +157,8 @@ export async function opStackFinalizer( signer ); - events.map(async (event, i) => { + const withdrawalStatuses: string[] = []; + await mapAsync(events, async (event, i) => { // Useful information for event: const l1TokenInfo = getL1TokenInfo(event.l2TokenAddress, chainId); const amountFromWei = convertFromWei(event.amountToReturn.toString(), l1TokenInfo.decimals); @@ -166,11 +167,12 @@ export async function opStackFinalizer( hash: event.transactionHash as `0x${string}`, }); const withdrawal = getWithdrawals(receipt)[logIndexesForMessage[i]]; - const withdrawalStatus = await publicClientL1.getWithdrawalStatus({ + const withdrawalStatus: GetWithdrawalStatusReturnType = await publicClientL1.getWithdrawalStatus({ receipt, targetChain: VIEM_OP_STACK_CHAINS[chainId], logIndex: logIndexesForMessage[i], }); + withdrawalStatuses.push(withdrawalStatus); if (withdrawalStatus === "ready-to-prove") { const l2Output = await publicClientL1.getL2Output({ // [!code hl] @@ -227,34 +229,40 @@ export async function opStackFinalizer( // console.log(`Executed finalization`, finalizeTxn); } }); - } - - const proofs = await multicallOptimismL1Proofs( - chainId, - recentTokensBridgedEvents, - crossChainMessenger, - hubPoolClient, - logger - ); - - // Next finalize withdrawals that have passed challenge period. - // Skip events that are likely not past the seven day challenge period. - logger.debug({ - at: "Finalizer", - message: `Earliest TokensBridged block to attempt to finalize for ${networkName}`, - earliestBlockToFinalize: latestBlockToProve, - }); + logger.debug({ + at: `${getNetworkName(chainId)}Finalizer`, + message: `${getNetworkName(chainId)} message statuses`, + statusesGrouped: _.countBy(withdrawalStatuses), + }); + callData = viemTxns.callData; + crossChainTransfers = viemTxns.withdrawals; + } else { + const proofs = await multicallOptimismL1Proofs( + chainId, + recentTokensBridgedEvents, + crossChainMessenger, + hubPoolClient, + logger + ); - const finalizations = await multicallOptimismFinalizations( - chainId, - olderTokensBridgedEvents, - crossChainMessenger, - hubPoolClient, - logger - ); + // Next finalize withdrawals that have passed challenge period. + // Skip events that are likely not past the seven day challenge period. + logger.debug({ + at: "Finalizer", + message: `Earliest TokensBridged block to attempt to finalize for ${networkName}`, + earliestBlockToFinalize: latestBlockToProve, + }); - const callData = viemTxns.callData; - const crossChainTransfers = viemTxns.withdrawals; + const finalizations = await multicallOptimismFinalizations( + chainId, + olderTokensBridgedEvents, + crossChainMessenger, + hubPoolClient, + logger + ); + callData = [...proofs.callData, ...finalizations.callData]; + crossChainTransfers = [...proofs.withdrawals, ...finalizations.withdrawals]; + } return { callData, crossChainMessages: crossChainTransfers }; } From 34e55f085d808c2493077eee2c13472cddb73cd8 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 22 Oct 2024 13:14:32 -0400 Subject: [PATCH 08/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index b218b859f..c9d4d7cf4 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -1,5 +1,5 @@ import assert from "assert"; -import { groupBy } from "lodash"; +import { groupBy, countBy } from "lodash"; import * as optimismSDK from "@eth-optimism/sdk"; import * as viem from "viem"; import * as viemChains from "viem/chains"; @@ -232,7 +232,7 @@ export async function opStackFinalizer( logger.debug({ at: `${getNetworkName(chainId)}Finalizer`, message: `${getNetworkName(chainId)} message statuses`, - statusesGrouped: _.countBy(withdrawalStatuses), + statusesGrouped: countBy(withdrawalStatuses), }); callData = viemTxns.callData; crossChainTransfers = viemTxns.withdrawals; From 0b227772cd4a48799ae6b0d3e05c6d06ece801b8 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 22 Oct 2024 14:58:22 -0400 Subject: [PATCH 09/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index c9d4d7cf4..185fa0ada 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -187,7 +187,7 @@ export async function opStackFinalizer( const callData = await crossChainMessenger.populateTransaction.proveWithdrawalTransaction(...proofArgs); console.log(`Withdrawal ${event.transactionHash} is ready to prove: `, proofArgs, callData); viemTxns.callData.push({ - callData: callData as any, + callData: (callData as any).data, target: crossChainMessenger.address, }); viemTxns.withdrawals.push({ @@ -215,7 +215,7 @@ export async function opStackFinalizer( const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); console.log(`Withdrawal ${event.transactionHash} is ready to finalize with args: `, withdrawal, callData); viemTxns.callData.push({ - callData: callData as any, + callData: (callData as any).data, target: crossChainMessenger.address, }); viemTxns.withdrawals.push({ From a9f6dbcbc6fa2a30fd6476a76b56ea34793e61b1 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 22 Oct 2024 17:04:14 -0400 Subject: [PATCH 10/21] Fix --- src/common/abi/OpStackPortalL1.json | 257 ++++++++++++++-------------- src/finalizer/utils/opStack.ts | 28 ++- 2 files changed, 143 insertions(+), 142 deletions(-) diff --git a/src/common/abi/OpStackPortalL1.json b/src/common/abi/OpStackPortalL1.json index d3e912594..3de119d4e 100644 --- a/src/common/abi/OpStackPortalL1.json +++ b/src/common/abi/OpStackPortalL1.json @@ -1,129 +1,134 @@ [ - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct Types.WithdrawalTransaction", - "name": "_tx", - "type": "tuple" - } + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } ], - "name": "finalizeWithdrawalTransaction", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - }, - { - "inputs": [ - { - "components": [ - { - "internalType": "uint256", - "name": "nonce", - "type": "uint256" - }, - { - "internalType": "address", - "name": "sender", - "type": "address" - }, - { - "internalType": "address", - "name": "target", - "type": "address" - }, - { - "internalType": "uint256", - "name": "value", - "type": "uint256" - }, - { - "internalType": "uint256", - "name": "gasLimit", - "type": "uint256" - }, - { - "internalType": "bytes", - "name": "data", - "type": "bytes" - } - ], - "internalType": "struct Types.WithdrawalTransaction", - "name": "_tx", - "type": "tuple" - }, - { - "internalType": "uint256", - "name": "_disputeGameIndex", - "type": "uint256" - }, - { - "components": [ - { - "internalType": "bytes32", - "name": "version", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "stateRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "messagePasserStorageRoot", - "type": "bytes32" - }, - { - "internalType": "bytes32", - "name": "latestBlockhash", - "type": "bytes32" - } - ], - "internalType": "struct Types.OutputRootProof", - "name": "_outputRootProof", - "type": "tuple" - }, - { - "internalType": "bytes[]", - "name": "_withdrawalProof", - "type": "bytes[]" - } + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + }, + { + "internalType": "address", + "name": "_proofSubmitter", + "type": "address" + } + ], + "name": "finalizeWithdrawalTransactionExternalProof", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, + { + "inputs": [ + { + "components": [ + { + "internalType": "uint256", + "name": "nonce", + "type": "uint256" + }, + { + "internalType": "address", + "name": "sender", + "type": "address" + }, + { + "internalType": "address", + "name": "target", + "type": "address" + }, + { + "internalType": "uint256", + "name": "value", + "type": "uint256" + }, + { + "internalType": "uint256", + "name": "gasLimit", + "type": "uint256" + }, + { + "internalType": "bytes", + "name": "data", + "type": "bytes" + } ], - "name": "proveWithdrawalTransaction", - "outputs": [], - "stateMutability": "nonpayable", - "type": "function" - } -] \ No newline at end of file + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + }, + { + "internalType": "uint256", + "name": "_disputeGameIndex", + "type": "uint256" + }, + { + "components": [ + { + "internalType": "bytes32", + "name": "version", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "stateRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "messagePasserStorageRoot", + "type": "bytes32" + }, + { + "internalType": "bytes32", + "name": "latestBlockhash", + "type": "bytes32" + } + ], + "internalType": "struct Types.OutputRootProof", + "name": "_outputRootProof", + "type": "tuple" + }, + { + "internalType": "bytes[]", + "name": "_withdrawalProof", + "type": "bytes[]" + } + ], + "name": "proveWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + } +] diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 185fa0ada..67e120c7b 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -175,17 +175,15 @@ export async function opStackFinalizer( withdrawalStatuses.push(withdrawalStatus); if (withdrawalStatus === "ready-to-prove") { const l2Output = await publicClientL1.getL2Output({ - // [!code hl] - l2BlockNumber: event.blockNumber, // [!code hl] - targetChain: VIEM_OP_STACK_CHAINS[chainId], // [!code hl] - }); // + l2BlockNumber: event.blockNumber, + targetChain: VIEM_OP_STACK_CHAINS[chainId], + }); const { l2OutputIndex, outputRootProof, withdrawalProof } = await (publicClientL2 as any).buildProveWithdrawal({ withdrawal, output: l2Output, }); const proofArgs = [withdrawal, l2OutputIndex, outputRootProof, withdrawalProof]; const callData = await crossChainMessenger.populateTransaction.proveWithdrawalTransaction(...proofArgs); - console.log(`Withdrawal ${event.transactionHash} is ready to prove: `, proofArgs, callData); viemTxns.callData.push({ callData: (callData as any).data, target: crossChainMessenger.address, @@ -198,22 +196,22 @@ export async function opStackFinalizer( miscReason: "proof", destinationChainId: hubPoolClient.chainId, }); - // const proveTxn = await(await crossChainMessenger.proveWithdrawalTransaction(...proofArgs)).wait(); - // console.log(`Submitted proof`, proveTxn); } else if (withdrawalStatus === "waiting-to-finalize") { const { seconds } = await publicClientL1.getTimeToFinalize({ withdrawalHash: withdrawal.withdrawalHash, targetChain: VIEM_OP_STACK_CHAINS[chainId], }); - // console.log(`Withdrawal hash: ${event.transactionHash} for withdrawal of ${event.amountToReturn.toString()} of ${event.l2TokenAddress}`); - console.log( - `Withdrawal ${event.transactionHash} for ${amountFromWei} of ${ + logger.debug({ + at: `${getNetworkName(chainId)}Finalizer`, + message: `Withdrawal ${event.transactionHash} for ${amountFromWei} of ${ l1TokenInfo.symbol - } is in challenge period for ${seconds / 60 / 60} hours` - ); + } is in challenge period for ${seconds / 60 / 60} hours`, + }); } else if (withdrawalStatus === "ready-to-finalize") { - const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); - console.log(`Withdrawal ${event.transactionHash} is ready to finalize with args: `, withdrawal, callData); + const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransactionExternalProof( + withdrawal, + await signer.getAddress() + ); viemTxns.callData.push({ callData: (callData as any).data, target: crossChainMessenger.address, @@ -225,8 +223,6 @@ export async function opStackFinalizer( type: "withdrawal", destinationChainId: hubPoolClient.chainId, }); - // const finalizeTxn = await(await crossChainMessenger.finalizeWithdrawalTransaction(withdrawal)).wait(); - // console.log(`Executed finalization`, finalizeTxn); } }); logger.debug({ From 6bd9e1518c16bc55e4959408c10eea11b4b59bbe Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Tue, 22 Oct 2024 17:05:07 -0400 Subject: [PATCH 11/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 67e120c7b..367ab923b 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -208,6 +208,7 @@ export async function opStackFinalizer( } is in challenge period for ${seconds / 60 / 60} hours`, }); } else if (withdrawalStatus === "ready-to-finalize") { + // @dev Note this assumes the signer submitted the proof. const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransactionExternalProof( withdrawal, await signer.getAddress() From 11cc9e8be33991e9620bff81ac9ea36861d20780 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 23 Oct 2024 09:45:37 -0400 Subject: [PATCH 12/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index c01e02332..9327c9728 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -87,7 +87,7 @@ export async function opStackFinalizer( // - Don't submit proofs for finalizations older than 1 day // - Don't try to withdraw tokens that are not past the 7 day challenge period const redis = await getRedisCache(logger); - const minimumFinalizationTime = getCurrentTime() - 32 * 3600 * 24; + const minimumFinalizationTime = getCurrentTime() - 7 * 3600 * 24; const latestBlockToProve = await getBlockForTimestamp(chainId, minimumFinalizationTime, undefined, redis); const { recentTokensBridgedEvents = [], olderTokensBridgedEvents = [] } = groupBy( spokePoolClient.getTokensBridged().filter( From 82850f0cc5ad660629af5ead599c8f9ae9b858bb Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 23 Oct 2024 09:46:22 -0400 Subject: [PATCH 13/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 9327c9728..72f43c376 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -111,7 +111,7 @@ export async function opStackFinalizer( message: `Latest TokensBridged block to attempt to submit proofs for ${networkName}`, latestBlockToProve, }); - + // Experimental feature: Add in all ETH withdrawals from OPStack chain to the finalizer. This will help us // in the short term to automate ETH withdrawals from Lite chains, which can build up ETH balances over time // and because they are lite chains, our only way to withdraw them is to initiate a slow bridge of ETH from the From 1ec0613df5ee524a5b7fa2115e96b007fc425d04 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 23 Oct 2024 09:52:16 -0400 Subject: [PATCH 14/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 72f43c376..f396c0ff9 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -198,7 +198,7 @@ export async function opStackFinalizer( logIndexesForMessage.push(logIndex); uniqueTokenhashes[event.transactionHash] += 1; } - + const crossChainMessenger = new Contract( VIEM_OP_STACK_CHAINS[chainId].contracts.portal[hubChainId].address, OPStackPortalL1, From f32f35ffd2cf919687ff81422e27f284993e4372 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 23 Oct 2024 12:31:44 -0400 Subject: [PATCH 15/21] Add op stack support --- src/common/abi/OpStackPortalL1.json | 21 ++++++++++++++++++ src/finalizer/utils/opStack.ts | 33 ++++++++++++++++++----------- 2 files changed, 42 insertions(+), 12 deletions(-) diff --git a/src/common/abi/OpStackPortalL1.json b/src/common/abi/OpStackPortalL1.json index 3de119d4e..95e37214c 100644 --- a/src/common/abi/OpStackPortalL1.json +++ b/src/common/abi/OpStackPortalL1.json @@ -49,6 +49,27 @@ "stateMutability": "nonpayable", "type": "function" }, + { + "inputs": [ + { + "components": [ + { "internalType": "uint256", "name": "nonce", "type": "uint256" }, + { "internalType": "address", "name": "sender", "type": "address" }, + { "internalType": "address", "name": "target", "type": "address" }, + { "internalType": "uint256", "name": "value", "type": "uint256" }, + { "internalType": "uint256", "name": "gasLimit", "type": "uint256" }, + { "internalType": "bytes", "name": "data", "type": "bytes" } + ], + "internalType": "struct Types.WithdrawalTransaction", + "name": "_tx", + "type": "tuple" + } + ], + "name": "finalizeWithdrawalTransaction", + "outputs": [], + "stateMutability": "nonpayable", + "type": "function" + }, { "inputs": [ { diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index f396c0ff9..5acb43405 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -52,12 +52,17 @@ const OP_STACK_CHAINS = Object.values(CHAIN_IDs).filter((chainId) => chainIsOPSt // We might want to export this mapping of chain ID to viem chain object out of a constant // file once we start using Viem elsewhere in the repo: const VIEM_OP_STACK_CHAINS = { + [CHAIN_IDs.OPTIMISM_SEPOLIA]: viemChains.optimismSepolia, [CHAIN_IDs.OPTIMISM]: viemChains.optimism, - // [CHAIN_IDs.BASE]: viemChains.base, + [CHAIN_IDs.BASE]: viemChains.base, + [CHAIN_IDs.REDSTONE]: viemChains.redstone, + [CHAIN_IDs.LISK]: viemChains.lisk, + [CHAIN_IDs.ZORA]: viemChains.zora, + [CHAIN_IDs.MODE]: viemChains.mode, + // @dev The following chains do not have "portal" contracts listed in the Viem chain definitions. + // They have non-standard interfaces for withdrawing from L2 to L1 + // [CHAIN_IDs.WORLD_CHAIN]: viemChains.worldchain, // [CHAIN_IDs.BLAST]: viemChains.blast, - // [CHAIN_IDs.REDSTONE]: viemChains.redstone, - // [CHAIN_IDs.LISK]: viemChains.lisk, - [CHAIN_IDs.OPTIMISM_SEPOLIA]: viemChains.optimismSepolia, }; type OVM_CHAIN_ID = (typeof OP_STACK_CHAINS)[number]; @@ -189,9 +194,7 @@ export async function opStackFinalizer( .extend(publicActionsL2() as any) as any; const uniqueTokenhashes = {}; const logIndexesForMessage = []; - const events = recentTokensBridgedEvents - .concat(olderTokensBridgedEvents) - .filter((e) => e.blockNumber <= latestBlockToProve); + const events = recentTokensBridgedEvents.concat(olderTokensBridgedEvents); for (const event of events) { uniqueTokenhashes[event.transactionHash] = uniqueTokenhashes[event.transactionHash] ?? 0; const logIndex = uniqueTokenhashes[event.transactionHash]; @@ -256,11 +259,17 @@ export async function opStackFinalizer( } is in challenge period for ${seconds / 60 / 60} hours`, }); } else if (withdrawalStatus === "ready-to-finalize") { - // @dev Note this assumes the signer submitted the proof. - const callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransactionExternalProof( - withdrawal, - await signer.getAddress() - ); + // @dev The Optimism portal requires that the msg.sender of the finalizeWithdrawalTransaction is equal + // to the address that submitted the proof. Other OpStack chains do not currently have this requirement as of + // October 2024 so we'll call the special function for Optimism only, which assumes the signer + // submitted the proof. + const callData = + chainId === CHAIN_IDs.OPTIMISM + ? await crossChainMessenger.populateTransaction.finalizeWithdrawalTransactionExternalProof( + withdrawal, + await signer.getAddress() + ) + : await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); viemTxns.callData.push({ callData: (callData as any).data, target: crossChainMessenger.address, From d3801949e5e114eccb14022237fd90ece4b14098 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 23 Oct 2024 16:34:00 -0400 Subject: [PATCH 16/21] wip --- src/common/abi/OpStackPortalL1.json | 17 +++++++++++++++++ src/finalizer/utils/opStack.ts | 25 ++++++++++++++++--------- 2 files changed, 33 insertions(+), 9 deletions(-) diff --git a/src/common/abi/OpStackPortalL1.json b/src/common/abi/OpStackPortalL1.json index 95e37214c..8da7b7e8c 100644 --- a/src/common/abi/OpStackPortalL1.json +++ b/src/common/abi/OpStackPortalL1.json @@ -151,5 +151,22 @@ "outputs": [], "stateMutability": "nonpayable", "type": "function" + }, + { + "inputs": [{ "internalType": "bytes32", "name": "_withdrawalHash", "type": "bytes32" }], + "name": "numProofSubmitters", + "outputs": [{ "internalType": "uint256", "name": "", "type": "uint256" }], + "stateMutability": "view", + "type": "function" + }, + { + "inputs": [ + { "internalType": "bytes32", "name": "", "type": "bytes32" }, + { "internalType": "uint256", "name": "", "type": "uint256" } + ], + "name": "proofSubmitters", + "outputs": [{ "internalType": "address", "name": "", "type": "address" }], + "stateMutability": "view", + "type": "function" } ] diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 5acb43405..b799c08e0 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -261,15 +261,22 @@ export async function opStackFinalizer( } else if (withdrawalStatus === "ready-to-finalize") { // @dev The Optimism portal requires that the msg.sender of the finalizeWithdrawalTransaction is equal // to the address that submitted the proof. Other OpStack chains do not currently have this requirement as of - // October 2024 so we'll call the special function for Optimism only, which assumes the signer - // submitted the proof. - const callData = - chainId === CHAIN_IDs.OPTIMISM - ? await crossChainMessenger.populateTransaction.finalizeWithdrawalTransactionExternalProof( - withdrawal, - await signer.getAddress() - ) - : await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); + // October 2024 so we'll call the special function for Optimism only, and look up who the original proof + // submitter was, which could be the Multicall2 contract. + let callData; + if (chainId === CHAIN_IDs.OPTIMISM) { + const numProofSubmitters = await crossChainMessenger.numProofSubmitters(withdrawal.withdrawalHash); + const proofSubmitter = await crossChainMessenger.proofSubmitters( + withdrawal.withdrawalHash, + numProofSubmitters - 1 + ); + callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransactionExternalProof( + withdrawal, + proofSubmitter + ); + } else { + callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); + } viemTxns.callData.push({ callData: (callData as any).data, target: crossChainMessenger.address, From aa365bd7e85325c5c72f6dc3318a0a790a88ddea Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 30 Oct 2024 12:48:21 -0400 Subject: [PATCH 17/21] bump to support base fraud proofs https://github.com/wevm/viem/releases/tag/viem%402.21.35 this viem release includes new fraud proof support for base --- package.json | 2 +- yarn.lock | 8 ++++---- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/package.json b/package.json index daae46464..8e75d6e59 100644 --- a/package.json +++ b/package.json @@ -39,7 +39,7 @@ "redis4": "npm:redis@^4.1.0", "superstruct": "^1.0.3", "ts-node": "^10.9.1", - "viem": "^2.21.30", + "viem": "^2.21.37", "winston": "^3.10.0", "zksync-ethers": "^5.7.2" }, diff --git a/yarn.lock b/yarn.lock index 5e54c4a88..09eacdb1d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -14707,10 +14707,10 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" -viem@^2.21.30: - version "2.21.32" - resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.32.tgz#b7f43b2004967036f83500260290cee45189f62a" - integrity sha512-2oXt5JNIb683oy7C8wuIJ/SeL3XtHVMEQpy1U2TA6WMnJQ4ScssRvyPwYLcaP6mKlrGXE/cR/V7ncWpvLUVPYQ== +viem@^2.21.37: + version "2.21.37" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.37.tgz#4d67bee8749321b0fd142c54df2021d5774403b1" + integrity sha512-JupwyttT4aJNnP9+kD7E8jorMS5VmgpC3hm3rl5zXsO8WNBTsP3JJqZUSg4AG6s2lTrmmpzS/qpmXMZu5gJw5Q== dependencies: "@adraffy/ens-normalize" "1.11.0" "@noble/curves" "1.6.0" From a423a377ca182ed7bcfaefc2833f9a14cd2875ab Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Wed, 30 Oct 2024 13:07:53 -0400 Subject: [PATCH 18/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 15 +++++++++------ 1 file changed, 9 insertions(+), 6 deletions(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index b799c08e0..224be06bc 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -259,12 +259,14 @@ export async function opStackFinalizer( } is in challenge period for ${seconds / 60 / 60} hours`, }); } else if (withdrawalStatus === "ready-to-finalize") { - // @dev The Optimism portal requires that the msg.sender of the finalizeWithdrawalTransaction is equal - // to the address that submitted the proof. Other OpStack chains do not currently have this requirement as of - // October 2024 so we'll call the special function for Optimism only, and look up who the original proof - // submitter was, which could be the Multicall2 contract. + // @dev Some OpStack chains use OptimismPortal instead of the newer OptimismPortal2, the latter of which + // requires that the msg.sender of the finalizeWithdrawalTransaction is equal to the address that + // submitted the proof. We try-catch both calls to handle this. + // See this comment in OptimismPortal2 for more context on why the new portal requires checking the + // proof submitter address: https://github.com/ethereum-optimism/optimism/blob/d6bda0339005d98c992c749c137938d515755029/packages/contracts-bedrock/src/L1/OptimismPortal2.sol#L132 let callData; - if (chainId === CHAIN_IDs.OPTIMISM) { + try { + // Calling OptimismPortal2: https://github.com/ethereum-optimism/optimism/blob/d6bda0339005d98c992c749c137938d515755029/packages/contracts-bedrock/src/L1/OptimismPortal2.sol const numProofSubmitters = await crossChainMessenger.numProofSubmitters(withdrawal.withdrawalHash); const proofSubmitter = await crossChainMessenger.proofSubmitters( withdrawal.withdrawalHash, @@ -274,7 +276,8 @@ export async function opStackFinalizer( withdrawal, proofSubmitter ); - } else { + } catch (e) { + // Calling OptimismPortal: https://github.com/ethereum-optimism/optimism/blob/d6bda0339005d98c992c749c137938d515755029/packages/contracts-bedrock/src/L1/OptimismPortal.sol callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); } viemTxns.callData.push({ From f79ee1bdaf038923fdf0573d22b8bb4714a69ed8 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Fri, 1 Nov 2024 12:07:32 -0400 Subject: [PATCH 19/21] Improve types --- src/finalizer/utils/opStack.ts | 30 +++++++++++++++++++++--------- 1 file changed, 21 insertions(+), 9 deletions(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 224be06bc..c2215459d 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -3,7 +3,14 @@ import { groupBy, countBy } from "lodash"; import * as optimismSDK from "@eth-optimism/sdk"; import * as viem from "viem"; import * as viemChains from "viem/chains"; -import { publicActionsL1, publicActionsL2, getWithdrawals, GetWithdrawalStatusReturnType } from "viem/op-stack"; +import { + publicActionsL1, + publicActionsL2, + getWithdrawals, + GetWithdrawalStatusReturnType, + PublicActionsL1, + PublicActionsL2, +} from "viem/op-stack"; import { HubPoolClient, SpokePoolClient } from "../../clients"; import { TokensBridged } from "../../interfaces"; import { @@ -180,18 +187,19 @@ export async function opStackFinalizer( }; if (VIEM_OP_STACK_CHAINS[chainId]) { const hubChainId = chainIsProd(chainId) ? CHAIN_IDs.MAINNET : CHAIN_IDs.SEPOLIA; + // TODO: can't figure out how to get these typecasts to work, despite this code being in the viem.opStack tutorials const publicClientL1 = viem .createPublicClient({ chain: chainIsProd(chainId) ? viemChains.mainnet : viemChains.sepolia, transport: viem.http(getCachedProvider(hubChainId, true).providers[0].connection.url), }) - .extend(publicActionsL1() as any) as any; + .extend(publicActionsL1() as any) as unknown as viem.PublicClient & PublicActionsL1; const publicClientL2 = viem .createPublicClient({ chain: VIEM_OP_STACK_CHAINS[chainId], transport: viem.http(getCachedProvider(chainId, true).providers[0].connection.url), }) - .extend(publicActionsL2() as any) as any; + .extend(publicActionsL2() as any) as unknown as viem.PublicClient & PublicActionsL2; const uniqueTokenhashes = {}; const logIndexesForMessage = []; const events = recentTokensBridgedEvents.concat(olderTokensBridgedEvents); @@ -214,29 +222,32 @@ export async function opStackFinalizer( const l1TokenInfo = getL1TokenInfo(event.l2TokenAddress, chainId); const amountFromWei = convertFromWei(event.amountToReturn.toString(), l1TokenInfo.decimals); - const receipt = await (publicClientL2 as viem.PublicClient).getTransactionReceipt({ + const receipt = await publicClientL2.getTransactionReceipt({ hash: event.transactionHash as `0x${string}`, }); const withdrawal = getWithdrawals(receipt)[logIndexesForMessage[i]]; const withdrawalStatus: GetWithdrawalStatusReturnType = await publicClientL1.getWithdrawalStatus({ receipt, + chain: undefined, targetChain: VIEM_OP_STACK_CHAINS[chainId], logIndex: logIndexesForMessage[i], }); withdrawalStatuses.push(withdrawalStatus); if (withdrawalStatus === "ready-to-prove") { const l2Output = await publicClientL1.getL2Output({ - l2BlockNumber: event.blockNumber, + chain: undefined, + l2BlockNumber: BigInt(event.blockNumber), targetChain: VIEM_OP_STACK_CHAINS[chainId], }); - const { l2OutputIndex, outputRootProof, withdrawalProof } = await (publicClientL2 as any).buildProveWithdrawal({ + const { l2OutputIndex, outputRootProof, withdrawalProof } = await publicClientL2.buildProveWithdrawal({ + chain: undefined, withdrawal, output: l2Output, }); const proofArgs = [withdrawal, l2OutputIndex, outputRootProof, withdrawalProof]; const callData = await crossChainMessenger.populateTransaction.proveWithdrawalTransaction(...proofArgs); viemTxns.callData.push({ - callData: (callData as any).data, + callData: callData.data, target: crossChainMessenger.address, }); viemTxns.withdrawals.push({ @@ -249,6 +260,7 @@ export async function opStackFinalizer( }); } else if (withdrawalStatus === "waiting-to-finalize") { const { seconds } = await publicClientL1.getTimeToFinalize({ + chain: undefined, withdrawalHash: withdrawal.withdrawalHash, targetChain: VIEM_OP_STACK_CHAINS[chainId], }); @@ -264,7 +276,7 @@ export async function opStackFinalizer( // submitted the proof. We try-catch both calls to handle this. // See this comment in OptimismPortal2 for more context on why the new portal requires checking the // proof submitter address: https://github.com/ethereum-optimism/optimism/blob/d6bda0339005d98c992c749c137938d515755029/packages/contracts-bedrock/src/L1/OptimismPortal2.sol#L132 - let callData; + let callData: ethers.PopulatedTransaction; try { // Calling OptimismPortal2: https://github.com/ethereum-optimism/optimism/blob/d6bda0339005d98c992c749c137938d515755029/packages/contracts-bedrock/src/L1/OptimismPortal2.sol const numProofSubmitters = await crossChainMessenger.numProofSubmitters(withdrawal.withdrawalHash); @@ -281,7 +293,7 @@ export async function opStackFinalizer( callData = await crossChainMessenger.populateTransaction.finalizeWithdrawalTransaction(withdrawal); } viemTxns.callData.push({ - callData: (callData as any).data, + callData: callData.data, target: crossChainMessenger.address, }); viemTxns.withdrawals.push({ From 8565ee463de2793764128c873294a0cda183d249 Mon Sep 17 00:00:00 2001 From: nicholaspai <9457025+nicholaspai@users.noreply.github.com> Date: Mon, 16 Dec 2024 20:11:48 -0500 Subject: [PATCH 20/21] Update opStack.ts --- src/finalizer/utils/opStack.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/finalizer/utils/opStack.ts b/src/finalizer/utils/opStack.ts index 309486f27..ea84dc115 100644 --- a/src/finalizer/utils/opStack.ts +++ b/src/finalizer/utils/opStack.ts @@ -36,7 +36,7 @@ import { mapAsync, paginatedEventQuery, } from "../../utils"; -import { CONTRACT_ADDRESSES, Multicall2Call, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; +import { CONTRACT_ADDRESSES, OPSTACK_CONTRACT_OVERRIDES } from "../../common"; import OPStackPortalL1 from "../../common/abi/OpStackPortalL1.json"; import { FinalizerPromise, CrossChainMessage } from "../types"; const { utils } = ethers; From 2607782e2712ea0dedb7263ec486fa3a41732d56 Mon Sep 17 00:00:00 2001 From: nicholaspai Date: Mon, 16 Dec 2024 20:20:09 -0500 Subject: [PATCH 21/21] Update yarn.lock --- yarn.lock | 59 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 59 insertions(+) diff --git a/yarn.lock b/yarn.lock index ae0a3c9ae..b9e80365c 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1561,6 +1561,13 @@ dependencies: "@noble/hashes" "1.5.0" +"@noble/curves@1.7.0", "@noble/curves@~1.7.0": + version "1.7.0" + resolved "https://registry.yarnpkg.com/@noble/curves/-/curves-1.7.0.tgz#0512360622439256df892f21d25b388f52505e45" + integrity sha512-UTMhXK9SeDhFJVrHeUJ5uZlI6ajXg10O6Ddocf9S6GjbSBVZsJo88HzKwXznNfGpMTRDyJkqMjNDPYgf0qFWnw== + dependencies: + "@noble/hashes" "1.6.0" + "@noble/hashes@1.0.0", "@noble/hashes@~1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.0.0.tgz#d5e38bfbdaba174805a4e649f13be9a9ed3351ae" @@ -1571,6 +1578,16 @@ resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.5.0.tgz#abadc5ca20332db2b1b2aa3e496e9af1213570b0" integrity sha512-1j6kQFb7QRru7eKN3ZDvRcP13rugwdxZqCjbiAVZfIJwgj2A65UmT4TgARXGlXgnRkORLTDTrO19ZErt7+QXgA== +"@noble/hashes@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.0.tgz#d4bfb516ad6e7b5111c216a5cc7075f4cf19e6c5" + integrity sha512-YUULf0Uk4/mAA89w+k3+yUYh6NrEvxZa5T6SY3wlMvE2chHkxFUUIDI8/XW1QSC357iA5pSnqt7XEhvFOqmDyQ== + +"@noble/hashes@1.6.1", "@noble/hashes@~1.6.0": + version "1.6.1" + resolved "https://registry.yarnpkg.com/@noble/hashes/-/hashes-1.6.1.tgz#df6e5943edcea504bac61395926d6fd67869a0d5" + integrity sha512-pq5D8h10hHBjyqX+cfBm0i8JUXJ0UhczFc4r74zbuT9XgewFo2E3J1cOaGtdZynILNmQ685YWGzGE1Zv6io50w== + "@noble/secp256k1@1.5.5", "@noble/secp256k1@~1.5.2": version "1.5.5" resolved "https://registry.yarnpkg.com/@noble/secp256k1/-/secp256k1-1.5.5.tgz#315ab5745509d1a8c8e90d0bdf59823ccf9bcfc3" @@ -2446,6 +2463,11 @@ resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.1.8.tgz#8f23646c352f020c83bca750a82789e246d42b50" integrity sha512-6CyAclxj3Nb0XT7GHK6K4zK6k2xJm6E4Ft0Ohjt4WgegiFUHEtFb2CGzmPmGBwoIhrLsqNLYfLr04Y1GePrzZg== +"@scure/base@~1.2.1": + version "1.2.1" + resolved "https://registry.yarnpkg.com/@scure/base/-/base-1.2.1.tgz#dd0b2a533063ca612c17aa9ad26424a2ff5aa865" + integrity sha512-DGmGtC8Tt63J5GfHgfl5CuAXh96VF/LD8K9Hr/Gv0J2lAoRGlPOMpqMpMbCTOoOJMZCk2Xt+DskdDyn6dEFdzQ== + "@scure/bip32@1.0.1": version "1.0.1" resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.0.1.tgz#1409bdf9f07f0aec99006bb0d5827693418d3aa5" @@ -2464,6 +2486,15 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.7" +"@scure/bip32@1.6.0": + version "1.6.0" + resolved "https://registry.yarnpkg.com/@scure/bip32/-/bip32-1.6.0.tgz#6dbc6b4af7c9101b351f41231a879d8da47e0891" + integrity sha512-82q1QfklrUUdXJzjuRU7iG7D7XiFx5PHYVS0+oeNKhyDLT7WPqs6pBcM2W5ZdwOwKCwoE1Vy1se+DHjcXwCYnA== + dependencies: + "@noble/curves" "~1.7.0" + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + "@scure/bip39@1.0.0": version "1.0.0" resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.0.0.tgz#47504e58de9a56a4bbed95159d2d6829fa491bb0" @@ -2480,6 +2511,14 @@ "@noble/hashes" "~1.5.0" "@scure/base" "~1.1.8" +"@scure/bip39@1.5.0": + version "1.5.0" + resolved "https://registry.yarnpkg.com/@scure/bip39/-/bip39-1.5.0.tgz#c8f9533dbd787641b047984356531d84485f19be" + integrity sha512-Dop+ASYhnrwm9+HA/HwXg7j2ZqM6yk2fyLWb5znexjctFY3+E+eU8cIWI0Pql0Qx4hPZCijlGq4OL71g+Uz30A== + dependencies: + "@noble/hashes" "~1.6.0" + "@scure/base" "~1.2.1" + "@sentry/core@5.30.0": version "5.30.0" resolved "https://registry.yarnpkg.com/@sentry/core/-/core-5.30.0.tgz#6b203664f69e75106ee8b5a2fe1d717379b331f3" @@ -3766,6 +3805,11 @@ abitype@1.0.6, abitype@^1.0.6: resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.6.tgz#76410903e1d88e34f1362746e2d407513c38565b" integrity sha512-MMSqYh4+C/aVqI2RQaWqbvI4Kxo5cQV40WQ4QFtDnNzCkqChm8MuENhElmynZlO0qUy/ObkEUaXtKqYnx1Kp3A== +abitype@1.0.7: + version "1.0.7" + resolved "https://registry.yarnpkg.com/abitype/-/abitype-1.0.7.tgz#876a0005d211e1c9132825d45bcee7b46416b284" + integrity sha512-ZfYYSktDQUwc2eduYu8C4wOs+RDPmnRYMh7zNfzeMtGGgb0U+6tLGjixUic6mXf5xKKCcgT5Qp6cv39tOARVFw== + abort-controller@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/abort-controller/-/abort-controller-3.0.0.tgz#eaf54d53b62bae4138e809ca225c8439a6efb392" @@ -15272,6 +15316,21 @@ verror@1.10.0: core-util-is "1.0.2" extsprintf "^1.2.0" +viem@^2.21.15: + version "2.21.55" + resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.55.tgz#a57ad31fcf2a0f6c011b1909f02c94421ec4f781" + integrity sha512-PgXew7C11cAuEtOSgRyQx2kJxEOPUwIwZA9dMglRByqJuFVA7wSGZZOOo/93iylAA8E15bEdqy9xulU3oKZ70Q== + dependencies: + "@noble/curves" "1.7.0" + "@noble/hashes" "1.6.1" + "@scure/bip32" "1.6.0" + "@scure/bip39" "1.5.0" + abitype "1.0.7" + isows "1.0.6" + ox "0.1.2" + webauthn-p256 "0.0.10" + ws "8.18.0" + viem@^2.21.37: version "2.21.37" resolved "https://registry.yarnpkg.com/viem/-/viem-2.21.37.tgz#4d67bee8749321b0fd142c54df2021d5774403b1"