Skip to content

Commit

Permalink
Merge pull request #12 from base-org/add-useWriteProveWithdrawalTrans…
Browse files Browse the repository at this point in the history
…action

Add use write prove withdrawal transaction
  • Loading branch information
lukasrosario authored Nov 8, 2023
2 parents bf21aa3 + 53ddaa8 commit b12fade
Show file tree
Hide file tree
Showing 8 changed files with 271 additions and 175 deletions.
20 changes: 20 additions & 0 deletions src/hooks/L1/useBlockNumberOfLatestL2OutputProposal.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { l2OutputOracleABI } from '@eth-optimism/contracts-ts'
import type { Abi } from 'viem'
import { useConfig, useReadContract, type UseReadContractParameters, type UseReadContractReturnType } from 'wagmi'
import type { OpConfig } from '../../types/OpConfig.js'

export function useBlockNumberOfLatestL2OutputProposal(
{ l2ChainId, config, ...rest }: { l2ChainId: number; config?: OpConfig } & UseReadContractParameters,
) {
const opConfig = useConfig({ config })
const l2Chain = opConfig.l2chains[l2ChainId]
const result = useReadContract({
abi: l2OutputOracleABI,
address: l2Chain.l1Addresses.l2OutputOracle.address,
functionName: 'latestBlockNumber',
args: [],
...rest,
})

return result as UseReadContractReturnType<Abi, 'latestBlockNumber', [], bigint>
}
26 changes: 26 additions & 0 deletions src/hooks/L1/useGetL2OutputIndexAfter.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,26 @@
import { l2OutputOracleABI } from '@eth-optimism/contracts-ts'
import { useConfig, useReadContract, type UseReadContractParameters, type UseReadContractReturnType } from 'wagmi'
import type { OpConfig } from '../../types/OpConfig.js'

export function useGetL2OutputIndexAfter(
{ l2ChainId, blockNumber, config, ...rest }: {
blockNumber?: bigint
l2ChainId: number
config?: OpConfig
} & UseReadContractParameters,
) {
const opConfig = useConfig({ config })
const l2Chain = opConfig.l2chains[l2ChainId]
const result = useReadContract({
abi: l2OutputOracleABI,
address: l2Chain.l1Addresses.l2OutputOracle.address,
functionName: 'getL2OutputIndexAfter',
args: [blockNumber || 0n],
query: {
enabled: Boolean(blockNumber),
},
...rest,
})

return result as UseReadContractReturnType<typeof l2OutputOracleABI, 'getL2OutputIndexAfter', [bigint], bigint>
}
123 changes: 123 additions & 0 deletions src/hooks/L1/useProveWithdrawalArgs.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
import { l2OutputOracleABI } from '@eth-optimism/contracts-ts'
import { useMemo } from 'react'
import { type Hash, pad } from 'viem'
import { type Config, useBlock, usePublicClient, useReadContract, useWaitForTransactionReceipt } from 'wagmi'
import type { BedrockCrossChainMessageProof } from '../../types/BedrockCrossChainMessageProof.js'
import { getMessageSlot } from '../../util/getMessageSlot.js'
import { useMakeStateTrieProof } from '../../util/getStateTrieProof.js'
import { getWithdrawalMessage } from '../../util/getWithdrawalMessage.js'
import { hashWithdrawal } from '../../util/hashWithdrawal.js'
import { useOpConfig } from '../useOpConfig.js'
import { useBlockNumberOfLatestL2OutputProposal } from './useBlockNumberOfLatestL2OutputProposal.js'
import { useGetL2OutputIndexAfter } from './useGetL2OutputIndexAfter.js'

export function useProveWithdrawalArgs({
l2ChainId,
config,
l1WithdrawalTxHash,
}: {
l1WithdrawalTxHash: Hash
l2ChainId: number
config?: Config
}) {
const opConfig = useOpConfig({ config })
const l2Chain = opConfig.l2chains[l2ChainId]

if (!l2Chain) {
throw new Error('L2 chain not configured')
}

const l2PublicClient = usePublicClient({ chainId: l2Chain.chainId })

const { data: blockNumberOfLatestL2OutputProposal } = useBlockNumberOfLatestL2OutputProposal({
config: opConfig,
l2ChainId: l2ChainId,
})

const { data: withdrawalOutputIndex } = useGetL2OutputIndexAfter({
blockNumber: blockNumberOfLatestL2OutputProposal,
l2ChainId,
config: opConfig,
})

const { data: proposal } = useReadContract({
abi: l2OutputOracleABI,
address: l2Chain.l1Addresses.l2OutputOracle.address,
functionName: 'getL2Output',
})

const { data: withdrawalReceipt } = useWaitForTransactionReceipt({
hash: l1WithdrawalTxHash,
chainId: l2Chain.chainId,
})

const withdrawalMessage = useMemo(() => {
if (!withdrawalReceipt) {
return undefined
}
return getWithdrawalMessage(withdrawalReceipt, l2Chain.l2Addresses.l2L1MessagePasserAddress.address)
}, [withdrawalReceipt, l2Chain])

const messageBedrockOutput = useMemo(() => {
if (!proposal || !withdrawalOutputIndex) {
return undefined
}
return {
outputRoot: proposal.outputRoot,
l1Timestamp: proposal.timestamp,
l2BlockNumber: proposal.l2BlockNumber,
l2OutputIndex: withdrawalOutputIndex,
}
}, [withdrawalMessage, proposal, withdrawalOutputIndex])

const hashedWithdrawal = useMemo(() => {
if (withdrawalMessage === undefined) {
return undefined
}
return hashWithdrawal(withdrawalMessage)
}, [withdrawalMessage])

const messageSlot = useMemo(() => {
if (!hashedWithdrawal) {
return undefined
}
return getMessageSlot(hashedWithdrawal)
}, [hashedWithdrawal])

const stateTrieProof = useMakeStateTrieProof(
l2PublicClient,
blockNumberOfLatestL2OutputProposal,
l2Chain.l2Addresses.l2L1MessagePasserAddress.address,
messageSlot,
)

const { data: block } = useBlock({
chainId: l2Chain.chainId,
blockNumber: blockNumberOfLatestL2OutputProposal,
})

const bedrockProof = useMemo(() => {
if (!withdrawalMessage || !stateTrieProof || !block || !messageBedrockOutput) {
return undefined
}

const bedrockProof: BedrockCrossChainMessageProof = {
outputRootProof: {
version: pad('0x0'),
stateRoot: block.stateRoot,
messagePasserStorageRoot: stateTrieProof.storageRoot,
latestBlockhash: block.hash,
},
withdrawalProof: stateTrieProof.storageProof,
l2OutputIndex: messageBedrockOutput.l2OutputIndex,
}

return bedrockProof
}, [withdrawalMessage, blockNumberOfLatestL2OutputProposal, withdrawalOutputIndex])

return {
withdrawalMessage,
withdrawalOutputIndex,
bedrockProof,
}
}
145 changes: 14 additions & 131 deletions src/hooks/L1/useSimulateProveWithdrawalTransaction.ts
Original file line number Diff line number Diff line change
@@ -1,160 +1,43 @@
'use client'

import { l2OutputOracleABI, l2StandardBridgeABI, optimismPortalABI } from '@eth-optimism/contracts-ts'
import type { Config, ResolvedRegister } from '@wagmi/core'
import { useMemo } from 'react'
import { type Hash, pad } from 'viem'
import {
useBlock,
usePublicClient,
useReadContract,
useSimulateContract,
type UseSimulateContractReturnType,
useWaitForTransactionReceipt,
} from 'wagmi'
import type { BedrockCrossChainMessageProof } from '../../types/BedrockCrossChainMessageProof.js'
import type { UseSimulateOPActionBaseParameters } from '../../types/UseSimulateOPActionBaseParameters.js'
import type { UseSimulateOPActionBaseReturnType } from '../../types/UseSimulateOPActionBaseReturnType.js'
import { getMessageSlot } from '../../util/getMessageSlot.js'
import { useMakeStateTrieProof } from '../../util/getStateTrieProof.js'
import { getWithdrawalMessage } from '../../util/getWithdrawalMessage.js'
import { hashWithdrawal } from '../../util/hashWithdrawal.js'
import { optimismPortalABI } from '@eth-optimism/contracts-ts'
import { type Hash } from 'viem'
import { useSimulateContract, type UseSimulateContractParameters } from 'wagmi'
import { useOpConfig } from '../useOpConfig.js'
// import { getLatestProposedL2BlockNumber } from 'op-viem/actions/L1/getLatestProposedL2BlockNumber'
import { useProveWithdrawalArgs } from './useProveWithdrawalArgs.js'

const ABI = l2StandardBridgeABI
const FUNCTION = 'withdrawTo'

export type UseProveWithdrawalTransactionParameters<
config extends Config = ResolvedRegister['config'],
chainId extends config['chains'][number]['id'] | undefined = undefined,
> =
& UseSimulateOPActionBaseParameters<typeof ABI, typeof FUNCTION, config, chainId>
export type UseProveWithdrawalTransactionParameters =
& UseSimulateContractParameters
& {
args: {
l1WithdrawalTxHash: Hash
l2ChainId: number
}
}

export type UseProveWithdrawalTransactionReturnType<
config extends Config = ResolvedRegister['config'],
chainId extends config['chains'][number]['id'] | undefined = undefined,
> = UseSimulateOPActionBaseReturnType<typeof ABI, typeof FUNCTION, config, chainId>

/**
* Simulates a proof of ERC20 token withdrawal transaction to an L1 address.
* @param parameters - {@link UseProveWithdrawalTransactionParameters}
* @returns wagmi [useSimulateContract return type](https://alpha.wagmi.sh/react/api/hooks/useSimulateContract#return-type). {@link UseProveWithdrawalTransactionReturnType}
*/
export function useProveWithdrawalTransaction<
config extends Config = ResolvedRegister['config'],
chainId extends config['chains'][number]['id'] | undefined = undefined,
>(
{ args, query: queryOverride, config }: UseProveWithdrawalTransactionParameters<config, chainId>,
): UseSimulateContractReturnType {
export function useProveWithdrawalTransaction(
{ args, query: queryOverride, config }: UseProveWithdrawalTransactionParameters,
) {
const opConfig = useOpConfig({ config })
const l2Chain = opConfig.l2chains[args.l2ChainId]

if (!l2Chain) {
throw new Error('L2 chain not configured')
}

const l2PublicClient = usePublicClient({ chainId: l2Chain.chainId })

const { data: blockNumberOfLatestL2OutputProposal } = useReadContract({
abi: l2OutputOracleABI,
address: l2Chain.l1Addresses.l2OutputOracle.address,
functionName: 'latestBlockNumber',
args: [],
})

const { data: withdrawalOutputIndex } = useReadContract({
abi: l2OutputOracleABI,
address: l2Chain.l1Addresses.l2OutputOracle.address,
functionName: 'getL2OutputIndexAfter',
args: [blockNumberOfLatestL2OutputProposal || 0n],
query: {
enabled: Boolean(blockNumberOfLatestL2OutputProposal),
},
})

const { data: proposal } = useReadContract({
abi: l2OutputOracleABI,
address: l2Chain.l1Addresses.l2OutputOracle.address,
functionName: 'getL2Output',
})

const { data: withdrawalReceipt } = useWaitForTransactionReceipt({
hash: args.l1WithdrawalTxHash,
chainId: l2Chain.chainId,
})

const withdrawalMessage = useMemo(() => {
if (!withdrawalReceipt) {
return undefined
}
return getWithdrawalMessage(withdrawalReceipt, l2Chain.l2Addresses.l2L1MessagePasserAddress.address)
}, [withdrawalReceipt, l2Chain])

const messageBedrockOutput = useMemo(() => {
if (!proposal || !withdrawalOutputIndex) {
return undefined
}
return {
outputRoot: proposal.outputRoot,
l1Timestamp: proposal.timestamp,
l2BlockNumber: proposal.l2BlockNumber,
l2OutputIndex: withdrawalOutputIndex,
}
}, [withdrawalMessage, proposal, withdrawalOutputIndex])

const hashedWithdrawal = useMemo(() => {
if (withdrawalMessage === undefined) {
return undefined
}
return hashWithdrawal(withdrawalMessage)
}, [withdrawalMessage])

const messageSlot = useMemo(() => {
if (!hashedWithdrawal) {
return undefined
}
return getMessageSlot(hashedWithdrawal)
}, [hashedWithdrawal])

const stateTrieProof = useMakeStateTrieProof(
l2PublicClient,
blockNumberOfLatestL2OutputProposal,
l2Chain.l2Addresses.l2L1MessagePasserAddress.address,
messageSlot,
)

const { data: block } = useBlock({
chainId: l2Chain.chainId,
blockNumber: blockNumberOfLatestL2OutputProposal,
const { withdrawalMessage, withdrawalOutputIndex, bedrockProof } = useProveWithdrawalArgs({
l2ChainId: args.l2ChainId,
config: opConfig,
l1WithdrawalTxHash: args.l1WithdrawalTxHash,
})

const bedrockProof = useMemo(() => {
if (!withdrawalMessage || !stateTrieProof || !block || !messageBedrockOutput) {
return undefined
}

const bedrockProof: BedrockCrossChainMessageProof = {
outputRootProof: {
version: pad('0x0'),
stateRoot: block.stateRoot,
messagePasserStorageRoot: stateTrieProof.storageRoot,
latestBlockhash: block.hash,
},
withdrawalProof: stateTrieProof.storageProof,
l2OutputIndex: messageBedrockOutput.l2OutputIndex,
}

return bedrockProof
}, [withdrawalMessage, blockNumberOfLatestL2OutputProposal, withdrawalOutputIndex])

return useSimulateContract({
chainId: l2Chain.l1ChaindId,
abi: optimismPortalABI,
address: l2Chain.l1Addresses.portal.address,
functionName: 'proveWithdrawalTransaction',
Expand Down
Loading

0 comments on commit b12fade

Please sign in to comment.