diff --git a/.changeset/gold-points-tan.md b/.changeset/gold-points-tan.md new file mode 100644 index 0000000000..2976ae1068 --- /dev/null +++ b/.changeset/gold-points-tan.md @@ -0,0 +1,5 @@ +--- +"viem": patch +--- + +**OP Stack:** Added `proofSubmitter` parameter to `finalizeWithdrawal` for finalizing withdrawals under a different account. diff --git a/site/pages/op-stack/actions/estimateFinalizeWithdrawalGas.md b/site/pages/op-stack/actions/estimateFinalizeWithdrawalGas.md index 13de3d7575..49e15ba1dc 100644 --- a/site/pages/op-stack/actions/estimateFinalizeWithdrawalGas.md +++ b/site/pages/op-stack/actions/estimateFinalizeWithdrawalGas.md @@ -101,6 +101,23 @@ const hash = await client.estimateFinalizeWithdrawalGas({ }) ``` +### proofSubmitter (optional) + +- **Type:** `Address` + +The address of the proof submitter to use when finalizing the withdrawal. No-op when the OptimismPortal contract version is less than v3. + +If unspecified, the sending account is the proof submitter. + +```ts +const hash = await client.finalizeWithdrawal({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + proofSubmitter: '0xD15F47c16BD277ff2dee6a0bD4e418165231CB69', // [!code focus] + withdrawal: { /* ... */ }, + targetChain: optimism, +}) +``` + ### maxFeePerGas (optional) - **Type:** `bigint` diff --git a/site/pages/op-stack/actions/finalizeWithdrawal.md b/site/pages/op-stack/actions/finalizeWithdrawal.md index d4fd6e39e9..4bb2596741 100644 --- a/site/pages/op-stack/actions/finalizeWithdrawal.md +++ b/site/pages/op-stack/actions/finalizeWithdrawal.md @@ -7,7 +7,9 @@ description: Finalizes a withdrawal that occurred on an L2. Finalizes a withdrawal that occurred on an L2. Used in the Withdrawal flow. -Internally performs a contract write to the [`finalizeWithdrawalTransaction` function](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal.sol#L272) on the [Optimism Portal contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal.sol). +Internally performs a contract write to the [`finalizeWithdrawalTransaction` function](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal2.sol#L383) on the [Optimism Portal contract](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal2.sol). + +If the proof submitter is specified and the OptimismPortal contract version is v3 or greater, the [`finalizeWithdrawalTransactionExternalProof` function](https://github.com/ethereum-optimism/optimism/blob/develop/packages/contracts-bedrock/src/L1/OptimismPortal2.sol#L390) function will be used to finalize the withdrawal with the provided address as the proof submitter. ## Usage @@ -177,6 +179,23 @@ const hash = await client.finalizeWithdrawal({ }) ``` +### proofSubmitter (optional) + +- **Type:** `Address` + +The address of the proof submitter to use when finalizing the withdrawal. No-op when the OptimismPortal contract version is less than v3. + +If unspecified, the sending account is the proof submitter. + +```ts +const hash = await client.finalizeWithdrawal({ + account: '0xf39fd6e51aad88f6f4ce6ab8827279cfffb92266', + proofSubmitter: '0xD15F47c16BD277ff2dee6a0bD4e418165231CB69', // [!code focus] + withdrawal: { /* ... */ }, + targetChain: optimism, +}) +``` + ### maxFeePerGas (optional) - **Type:** `bigint` diff --git a/src/op-stack/actions/estimateFinalizeWithdrawalGas.test.ts b/src/op-stack/actions/estimateFinalizeWithdrawalGas.test.ts index 36f5e253b6..ee5c71f0b6 100644 --- a/src/op-stack/actions/estimateFinalizeWithdrawalGas.test.ts +++ b/src/op-stack/actions/estimateFinalizeWithdrawalGas.test.ts @@ -35,3 +35,33 @@ test('default', async () => { }) expect(gas).toBeDefined() }) + +test('args: proof submitter', async () => { + // TODO: migrate the rest of the test suite to a block with fault proofs + // Sample withdrawal with fault proofs: https://optimistic.etherscan.io/tx/0x039d2fdf3161910cb667ed599a0a899314bd5041797b6707ba312792b6d43b5c + await reset(client, { + blockNumber: 21165285n, + jsonRpcUrl: anvilMainnet.forkUrl, + }) + + const proofSubmitter = '0xD15F47c16BD277ff2dee6a0bD4e418165231CB69' + const withdrawal = { + nonce: + 1766847064778384329583297500742918515827483896875618958121606201292641117n, + sender: '0x4200000000000000000000000000000000000007', + target: '0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1', + value: 0n, + gasLimit: 3334219n, + data: '0xd764ad0b0001000000000000000000000000000000000000000000000000000000005352000000000000000000000000136b1ec699c62b0606854056f02dc7bb80482d6300000000000000000000000039ea01a0298c315d149a490e34b59dbf2ec7e48f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002dc6c000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000064a6492fe27355534400000000000000000000000000000000000000000000000000000000000000000000000000000000d15f47c16bd277ff2dee6a0bd4e418165231cb6900000000000000000000000000000000000000000000002e9ee5c38653f0000000000000000000000000000000000000000000000000000000000000', + withdrawalHash: + '0xD55C5BA792D04BC45656D5115F98A4AE446F66C857A41B6666C045023B7EBDB7', + } as const + + const gas = await estimateFinalizeWithdrawalGas(client, { + account: accounts[0].address, + targetChain: optimism, + withdrawal, + proofSubmitter, // on behalf of the proof submittor + }) + expect(gas).toBeDefined() +}) diff --git a/src/op-stack/actions/estimateFinalizeWithdrawalGas.ts b/src/op-stack/actions/estimateFinalizeWithdrawalGas.ts index a5590b2064..ba4f2bde46 100644 --- a/src/op-stack/actions/estimateFinalizeWithdrawalGas.ts +++ b/src/op-stack/actions/estimateFinalizeWithdrawalGas.ts @@ -15,7 +15,7 @@ import type { } from '../../types/chain.js' import type { UnionEvaluate, UnionOmit } from '../../types/utils.js' import type { FormattedTransactionRequest } from '../../utils/formatters/transactionRequest.js' -import { portalAbi } from '../abis.js' +import { portal2Abi, portalAbi } from '../abis.js' import type { GetContractAddressParameter } from '../types/contract.js' import type { Withdrawal } from '../types/withdrawal.js' @@ -42,6 +42,11 @@ export type EstimateFinalizeWithdrawalGasParameters< GetContractAddressParameter<_derivedChain, 'portal'> & { /** Gas limit for transaction execution on the L2. */ gas?: bigint | undefined + /** + * Finalize against a specific proof submitter. + * If unspecified, the sending account is the default. + */ + proofSubmitter?: Address | null | undefined withdrawal: Withdrawal } export type EstimateFinalizeWithdrawalGasReturnType = bigint @@ -93,6 +98,7 @@ export async function estimateFinalizeWithdrawalGas< maxFeePerGas, maxPriorityFeePerGas, nonce, + proofSubmitter, targetChain, withdrawal, } = parameters @@ -103,12 +109,20 @@ export async function estimateFinalizeWithdrawalGas< return Object.values(targetChain!.contracts.portal)[0].address })() + const [functionName, args, abi] = proofSubmitter + ? [ + 'finalizeWithdrawalTransactionExternalProof', + [withdrawal, proofSubmitter], + portal2Abi, + ] + : ['finalizeWithdrawalTransaction', [withdrawal], portalAbi] + const params = { account, - abi: portalAbi, + abi, address: portalAddress, - functionName: 'finalizeWithdrawalTransaction', - args: [withdrawal], + functionName, + args, gas, maxFeePerGas, maxPriorityFeePerGas, @@ -117,9 +131,7 @@ export async function estimateFinalizeWithdrawalGas< // in `estimateContractGas` or `estimateGas` // @ts-ignore chain, - } satisfies EstimateContractGasParameters< - typeof portalAbi, - 'finalizeWithdrawalTransaction' - > + } satisfies EstimateContractGasParameters + return estimateContractGas(client, params as any) } diff --git a/src/op-stack/actions/finalizeWithdrawal.test.ts b/src/op-stack/actions/finalizeWithdrawal.test.ts index 884662a2b9..b8af4eee3d 100644 --- a/src/op-stack/actions/finalizeWithdrawal.test.ts +++ b/src/op-stack/actions/finalizeWithdrawal.test.ts @@ -111,6 +111,43 @@ test('args: portal address', async () => { expect(receipt.status).toEqual('success') }) +test('args: proof submitter', async () => { + // TODO: migrate the rest of the test suite to a block with fault proofs + // Sample withdrawal with fault proofs: https://optimistic.etherscan.io/tx/0x039d2fdf3161910cb667ed599a0a899314bd5041797b6707ba312792b6d43b5c + await reset(client, { + blockNumber: 21165285n, + jsonRpcUrl: anvilMainnet.forkUrl, + }) + + const proofSubmitter = '0xD15F47c16BD277ff2dee6a0bD4e418165231CB69' + const withdrawal = { + nonce: + 1766847064778384329583297500742918515827483896875618958121606201292641117n, + sender: '0x4200000000000000000000000000000000000007', + target: '0x25ace71c97B33Cc4729CF772ae268934F7ab5fA1', + value: 0n, + gasLimit: 3334219n, + data: '0xd764ad0b0001000000000000000000000000000000000000000000000000000000005352000000000000000000000000136b1ec699c62b0606854056f02dc7bb80482d6300000000000000000000000039ea01a0298c315d149a490e34b59dbf2ec7e48f000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000002dc6c000000000000000000000000000000000000000000000000000000000000000c00000000000000000000000000000000000000000000000000000000000000064a6492fe27355534400000000000000000000000000000000000000000000000000000000000000000000000000000000d15f47c16bd277ff2dee6a0bd4e418165231cb6900000000000000000000000000000000000000000000002e9ee5c38653f0000000000000000000000000000000000000000000000000000000000000', + withdrawalHash: + '0xD55C5BA792D04BC45656D5115F98A4AE446F66C857A41B6666C045023B7EBDB7', + } as const + + const hash = await finalizeWithdrawal(client, { + account: accounts[0].address, + targetChain: optimism, + withdrawal, + proofSubmitter, // on behalf of the proof submittor + }) + expect(hash).toBeDefined() + + await mine(client, { blocks: 1 }) + + const receipt = await getTransactionReceipt(client, { + hash, + }) + expect(receipt.status).toEqual('success') +}) + test('error: small gas', async () => { await expect(() => finalizeWithdrawal(client, { diff --git a/src/op-stack/actions/finalizeWithdrawal.ts b/src/op-stack/actions/finalizeWithdrawal.ts index e8717005ee..e238a64195 100644 --- a/src/op-stack/actions/finalizeWithdrawal.ts +++ b/src/op-stack/actions/finalizeWithdrawal.ts @@ -16,7 +16,7 @@ import type { import type { Hash } from '../../types/misc.js' import type { UnionEvaluate, UnionOmit } from '../../types/utils.js' import type { FormattedTransactionRequest } from '../../utils/formatters/transactionRequest.js' -import { portalAbi } from '../abis.js' +import { portal2Abi, portalAbi } from '../abis.js' import type { GetContractAddressParameter } from '../types/contract.js' import type { Withdrawal } from '../types/withdrawal.js' import { @@ -51,6 +51,11 @@ export type FinalizeWithdrawalParameters< * `null` to skip gas estimation & defer calculation to signer. */ gas?: bigint | null | undefined + /** + * Finalize against a provided proof submitter. + * If unspecified, the sending account is the default. + */ + proofSubmitter?: Address | null | undefined withdrawal: Withdrawal } export type FinalizeWithdrawalReturnType = Hash @@ -98,6 +103,7 @@ export async function finalizeWithdrawal< maxFeePerGas, maxPriorityFeePerGas, nonce, + proofSubmitter, targetChain, withdrawal, } = parameters @@ -116,13 +122,21 @@ export async function finalizeWithdrawal< ) : undefined + const [functionName, args, abi] = proofSubmitter + ? [ + 'finalizeWithdrawalTransactionExternalProof', + [withdrawal, proofSubmitter], + portal2Abi, + ] + : ['finalizeWithdrawalTransaction', [withdrawal], portalAbi] + return writeContract(client, { account: account!, - abi: portalAbi, + abi, address: portalAddress, chain, - functionName: 'finalizeWithdrawalTransaction', - args: [withdrawal], + functionName, + args, gas: gas_, maxFeePerGas, maxPriorityFeePerGas,