From 79c44d742bf7ebd97394812974530a6e769d576e Mon Sep 17 00:00:00 2001 From: Daniel Wang <99078276+dantaik@users.noreply.github.com> Date: Wed, 20 Dec 2023 01:18:44 +0800 Subject: [PATCH] feat(bridge-ui-v2): L2 <-> L2 bridging with multi-hop (#15395) Co-authored-by: xiaodino --- packages/bridge-ui-v2/.env.example | 2 +- .../config/sample/configuredBridges.example | 18 +- .../schemas/configuredBridges.schema.json | 19 ++ .../vite-plugins/generateBridgeConfig.ts | 5 +- .../vite-plugins/generateChainConfig.ts | 2 +- .../vite-plugins/generateCustomTokenConfig.ts | 2 +- .../generateEventIndexerConfig.ts | 2 +- .../vite-plugins/generateRelayerConfig.ts | 2 +- .../src/libs/bridge/ERC1155Bridge.ts | 2 + .../src/libs/bridge/ERC20Bridge.ts | 14 +- .../src/libs/bridge/ERC721Bridge.ts | 2 + .../bridge-ui-v2/src/libs/bridge/types.ts | 9 + .../bridge-ui-v2/src/libs/error/errors.ts | 4 + .../src/libs/proof/BridgeProver.ts | 255 ++++++++++++++---- .../bridge-ui-v2/src/libs/proof/Prover.ts | 83 ------ packages/bridge-ui-v2/src/libs/proof/index.ts | 1 - packages/bridge-ui-v2/src/libs/proof/types.ts | 8 +- 17 files changed, 280 insertions(+), 150 deletions(-) delete mode 100644 packages/bridge-ui-v2/src/libs/proof/Prover.ts diff --git a/packages/bridge-ui-v2/.env.example b/packages/bridge-ui-v2/.env.example index 20f4b53ee4e..fef86610895 100644 --- a/packages/bridge-ui-v2/.env.example +++ b/packages/bridge-ui-v2/.env.example @@ -9,7 +9,7 @@ export PUBLIC_WALLETCONNECT_PROJECT_ID="" # Enable NFT Bridge ("true" or "false") export PUBLIC_NFT_BRIDGE_ENABLED="" -PUBLIC_NFT_BATCH_TRANSFERS_ENABLED="" +export PUBLIC_NFT_BATCH_TRANSFERS_ENABLED="" # Sentry export PUBLIC_SENTRY_DSN=https:// diff --git a/packages/bridge-ui-v2/config/sample/configuredBridges.example b/packages/bridge-ui-v2/config/sample/configuredBridges.example index 0fae45ec692..9c3f23b2322 100644 --- a/packages/bridge-ui-v2/config/sample/configuredBridges.example +++ b/packages/bridge-ui-v2/config/sample/configuredBridges.example @@ -33,7 +33,14 @@ "erc721VaultAddress": "", "erc1155VaultAddress": "", "crossChainSyncAddress": "", - "signalServiceAddress": "" + "signalServiceAddress": "", + "hops": [ + { + "chaind: , + "crossChainSyncAddress": "", + "signalServiceAddress": "", + } + ] } }, { @@ -45,7 +52,14 @@ "erc721VaultAddress": "", "erc1155VaultAddress": "", "crossChainSyncAddress": "", - "signalServiceAddress": "" + "signalServiceAddress": "", + "hops": [ + { + "chaind: , + "crossChainSyncAddress": "", + "signalServiceAddress": "", + } + ] } } ] diff --git a/packages/bridge-ui-v2/config/schemas/configuredBridges.schema.json b/packages/bridge-ui-v2/config/schemas/configuredBridges.schema.json index 9d5041e00e7..fa63413d2d0 100644 --- a/packages/bridge-ui-v2/config/schemas/configuredBridges.schema.json +++ b/packages/bridge-ui-v2/config/schemas/configuredBridges.schema.json @@ -36,6 +36,25 @@ }, "signalServiceAddress": { "type": "string" + }, + "hops": { + "type": "array", + "default": [], + "items": { + "type": "object", + "properties": { + "chainId": { + "type": "number" + }, + "crossChainSyncAddress": { + "type": "string" + }, + "signalServiceAddress": { + "type": "string" + } + }, + "required": ["chainId", "crossChainSyncAddress", "signalServiceAddress"] + } } }, "required": [ diff --git a/packages/bridge-ui-v2/scripts/vite-plugins/generateBridgeConfig.ts b/packages/bridge-ui-v2/scripts/vite-plugins/generateBridgeConfig.ts index d98f644a367..b6019998b19 100644 --- a/packages/bridge-ui-v2/scripts/vite-plugins/generateBridgeConfig.ts +++ b/packages/bridge-ui-v2/scripts/vite-plugins/generateBridgeConfig.ts @@ -136,10 +136,13 @@ async function buildBridgeConfig(sourceFile: SourceFile, configuredBridgesConfig } const _formatObjectToTsLiteral = (obj: RoutingMap): string => { - const formatValue = (value: string | number | boolean | null): string => { + const formatValue = (value: string | number | boolean | null | object): string => { if (typeof value === 'string') { return `"${value}"`; } + if (typeof value === 'object') { + return JSON.stringify(value); + } return String(value); }; diff --git a/packages/bridge-ui-v2/scripts/vite-plugins/generateChainConfig.ts b/packages/bridge-ui-v2/scripts/vite-plugins/generateChainConfig.ts index f49f42484db..7871193e795 100644 --- a/packages/bridge-ui-v2/scripts/vite-plugins/generateChainConfig.ts +++ b/packages/bridge-ui-v2/scripts/vite-plugins/generateChainConfig.ts @@ -39,7 +39,7 @@ export function generateChainConfig() { const isValid = validateJsonAgainstSchema(configuredChainsConfigFile, configuredChainsSchema); if (!isValid) { - throw new Error('encoded configuredBridges.json is not valid.'); + throw new Error('encoded configuredChains.json is not valid.'); } } else { configuredChainsConfigFile = ''; diff --git a/packages/bridge-ui-v2/scripts/vite-plugins/generateCustomTokenConfig.ts b/packages/bridge-ui-v2/scripts/vite-plugins/generateCustomTokenConfig.ts index 8ec175b10b2..f852d5d4080 100644 --- a/packages/bridge-ui-v2/scripts/vite-plugins/generateCustomTokenConfig.ts +++ b/packages/bridge-ui-v2/scripts/vite-plugins/generateCustomTokenConfig.ts @@ -43,7 +43,7 @@ export function generateCustomTokenConfig() { const isValid = validateJsonAgainstSchema(configuredTokenConfigFile, configuredChainsSchema); if (!isValid) { - throw new Error('encoded configuredBridges.json is not valid.'); + throw new Error('encoded generateCustomTokenConfig.json is not valid.'); } } const tsFilePath = path.resolve(outputPath); diff --git a/packages/bridge-ui-v2/scripts/vite-plugins/generateEventIndexerConfig.ts b/packages/bridge-ui-v2/scripts/vite-plugins/generateEventIndexerConfig.ts index 8a56c62903e..cc75ac298e8 100644 --- a/packages/bridge-ui-v2/scripts/vite-plugins/generateEventIndexerConfig.ts +++ b/packages/bridge-ui-v2/scripts/vite-plugins/generateEventIndexerConfig.ts @@ -44,7 +44,7 @@ export function generateEventIndexerConfig() { // Valide JSON against schema const isValid = validateJsonAgainstSchema(configuredEventIndexerConfigFile, configuredEventIndexerSchema); if (!isValid) { - throw new Error('encoded configuredBridges.json is not valid.'); + throw new Error('encoded configuredEventIndexer.json is not valid.'); } } // Path to where you want to save the generated Typ eScript file diff --git a/packages/bridge-ui-v2/scripts/vite-plugins/generateRelayerConfig.ts b/packages/bridge-ui-v2/scripts/vite-plugins/generateRelayerConfig.ts index d11c57002e2..28f8808523e 100644 --- a/packages/bridge-ui-v2/scripts/vite-plugins/generateRelayerConfig.ts +++ b/packages/bridge-ui-v2/scripts/vite-plugins/generateRelayerConfig.ts @@ -44,7 +44,7 @@ export function generateRelayerConfig() { // Valide JSON against schema const isValid = validateJsonAgainstSchema(configuredRelayerConfigFile, configuredRelayerSchema); if (!isValid) { - throw new Error('encoded configuredBridges.json is not valid.'); + throw new Error('encoded configuredRelayer.json is not valid.'); } } // Path to where you want to save the generated Typ eScript file diff --git a/packages/bridge-ui-v2/src/libs/bridge/ERC1155Bridge.ts b/packages/bridge-ui-v2/src/libs/bridge/ERC1155Bridge.ts index dbacc31b5de..05d8474b776 100644 --- a/packages/bridge-ui-v2/src/libs/bridge/ERC1155Bridge.ts +++ b/packages/bridge-ui-v2/src/libs/bridge/ERC1155Bridge.ts @@ -189,6 +189,7 @@ export class ERC1155Bridge extends Bridge { const { to, wallet, + srcChainId, destChainId, token, fee, @@ -214,6 +215,7 @@ export class ERC1155Bridge extends Bridge { : BigInt(0); const sendERC1155Args: NFTBridgeTransferOp = { + srcChainId: BigInt(srcChainId), destChainId: BigInt(destChainId), to, token, diff --git a/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts b/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts index ece1f98699d..db2e7ef875c 100644 --- a/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts +++ b/packages/bridge-ui-v2/src/libs/bridge/ERC20Bridge.ts @@ -32,7 +32,18 @@ const log = getLogger('ERC20Bridge'); export class ERC20Bridge extends Bridge { private static async _prepareTransaction(args: ERC20BridgeArgs) { - const { to, amount, wallet, destChainId, token, fee, tokenVaultAddress, isTokenAlreadyDeployed, memo = '' } = args; + const { + to, + amount, + wallet, + srcChainId, + destChainId, + token, + fee, + tokenVaultAddress, + isTokenAlreadyDeployed, + memo = '', + } = args; const tokenVaultContract = getContract({ walletClient: wallet, @@ -49,6 +60,7 @@ export class ERC20Bridge extends Bridge { : BigInt(0); const sendERC20Args: BridgeTransferOp = { + srcChainId: BigInt(srcChainId), destChainId: BigInt(destChainId), to, token, diff --git a/packages/bridge-ui-v2/src/libs/bridge/ERC721Bridge.ts b/packages/bridge-ui-v2/src/libs/bridge/ERC721Bridge.ts index 5c166ffb6b1..59b54826fff 100644 --- a/packages/bridge-ui-v2/src/libs/bridge/ERC721Bridge.ts +++ b/packages/bridge-ui-v2/src/libs/bridge/ERC721Bridge.ts @@ -195,6 +195,7 @@ export class ERC721Bridge extends Bridge { const { to, wallet, + srcChainId, destChainId, token, fee, @@ -220,6 +221,7 @@ export class ERC721Bridge extends Bridge { : BigInt(0); const sendERC721Args: NFTBridgeTransferOp = { + srcChainId: BigInt(srcChainId), destChainId: BigInt(destChainId), to, token, diff --git a/packages/bridge-ui-v2/src/libs/bridge/types.ts b/packages/bridge-ui-v2/src/libs/bridge/types.ts index 9c51bba7043..f5dd83ba415 100644 --- a/packages/bridge-ui-v2/src/libs/bridge/types.ts +++ b/packages/bridge-ui-v2/src/libs/bridge/types.ts @@ -78,6 +78,7 @@ export type BridgeTransaction = { }; export type BridgeTransferOp = { + srcChainId: bigint; destChainId: bigint; to: Address; token: Address; @@ -89,6 +90,7 @@ export type BridgeTransferOp = { }; export type NFTBridgeTransferOp = { + srcChainId: bigint; destChainId: bigint; to: Address; token: Address; @@ -200,6 +202,13 @@ export type AddressConfig = { erc1155VaultAddress: Address; crossChainSyncAddress: Address; signalServiceAddress: Address; + hops?: Array; +}; + +export type HopAddressConfig = { + chainId: number; + crossChainSyncAddress: Address; + signalServiceAddress: Address; }; export enum ContractType { diff --git a/packages/bridge-ui-v2/src/libs/error/errors.ts b/packages/bridge-ui-v2/src/libs/error/errors.ts index 749c97f78b8..4b169a3f073 100644 --- a/packages/bridge-ui-v2/src/libs/error/errors.ts +++ b/packages/bridge-ui-v2/src/libs/error/errors.ts @@ -74,6 +74,10 @@ export class InvalidProofError extends Error { name = 'InvalidProofError'; } +export class WrongBridgeConfigError extends Error { + name = 'WrongBridgeConfigError'; +} + export class MessageStatusError extends Error { name = 'MessageStatusError'; } diff --git a/packages/bridge-ui-v2/src/libs/proof/BridgeProver.ts b/packages/bridge-ui-v2/src/libs/proof/BridgeProver.ts index 6dede834536..f7f05d6f868 100644 --- a/packages/bridge-ui-v2/src/libs/proof/BridgeProver.ts +++ b/packages/bridge-ui-v2/src/libs/proof/BridgeProver.ts @@ -1,27 +1,209 @@ -import { type Address, encodeAbiParameters, type Hash, type Hex, toHex } from 'viem'; +import type { Hash } from '@wagmi/core'; +import { getContract, type GetContractResult, type PublicClient } from '@wagmi/core'; +import { type Address, encodeAbiParameters, encodePacked, type Hex, keccak256, numberToHex, toHex, toRlp } from 'viem'; +import { crossChainSyncABI } from '$abi'; import { routingContractsMap } from '$bridgeConfig'; import { MessageStatus } from '$libs/bridge'; -import { InvalidProofError } from '$libs/error'; +import { InvalidProofError, PendingBlockError, WrongBridgeConfigError } from '$libs/error'; +import { getLogger } from '$libs/util/logger'; +import { publicClient } from '$libs/wagmi'; -import { Prover } from './Prover'; +import type { ClientWithEthGetProofRequest, GenerateProofArgs, Hop } from './types'; -export class BridgeProver extends Prover { - constructor() { - super(); +const log = getLogger('proof:Prover'); + +export class BridgeProver { + async getSignalSlot(chainId: number, contractAddress: Address, msgHash: Hex) { + return keccak256( + encodePacked(['string', 'uint64', 'address', 'bytes32'], ['SIGNAL', BigInt(chainId), contractAddress, msgHash]), + ); + } + + protected async getBlockNumber(srcChainId: number, destChainId: number, crossChainSyncAddress: Address) { + const crossChainSyncContract = getContract({ + chainId: destChainId, + address: crossChainSyncAddress, + abi: crossChainSyncABI, + }); + const client = publicClient({ chainId: srcChainId }); + const syncedSnippet = await crossChainSyncContract.read.getSyncedSnippet([BigInt(0)]); + const latestBlockHash = syncedSnippet['blockHash']; + const block = await client.getBlock({ blockHash: latestBlockHash }); + if (block.hash === null || block.number === null) { + throw new PendingBlockError('block is pending'); + } + return block.number; + } + + protected async getBlockFromGetSyncedSnippet( + client: PublicClient, + crossChainSyncContract: GetContractResult, + blockNumber: number, + ) { + const syncedSnippet = await crossChainSyncContract.read.getSyncedSnippet([BigInt(blockNumber)]); + const latestBlockHash = syncedSnippet['blockHash']; + const block = await client.getBlock({ blockHash: latestBlockHash }); + return { block, syncedSnippet }; + } + + async encodedStorageProof(args: GenerateProofArgs) { + const { msgHash, clientChainId, contractAddress, proofForAccountAddress, blockNumber } = args; + const client = publicClient({ chainId: clientChainId }); + const key = await this.getSignalSlot(clientChainId, contractAddress, msgHash); + log('Signal slot', key); + + // Unfortunately, since this method is stagnant, it hasn't been included into Viem lib + // as supported methods. Still stupported by Alchmey, Infura and others. + // See https://eips.ethereum.org/EIPS/eip-1186 + // Following is a workaround to support this method. + const clientWithEthProofRequest = client as ClientWithEthGetProofRequest; + + // RPC call to get the merkle proof what value is at key on the SignalService contract + const proof = await clientWithEthProofRequest.request({ + method: 'eth_getProof', + params: [ + // Address of the account to get the proof for + proofForAccountAddress, + + // Array of storage-keys that should be proofed and included + [key], + + numberToHex(blockNumber as bigint), + ], + }); + + log('Proof from eth_getProof', proof); + + if (proof.storageProof[0].value !== toHex(true)) { + throw new InvalidProofError('storage proof value is not 1'); + } + + // RLP encode the proof together for LibTrieProof to decode + const rlpEncodedStorageProof = toRlp(proof.storageProof[0].proof); + + return { proof, rlpEncodedStorageProof }; + } + + async encodedSignalProof(msgHash: Hash, srcChainId: number, destChainId: number) { + const hops = routingContractsMap[srcChainId][destChainId].hops; + if (hops && hops.length > 0) { + return await this._encodedSignalProofWithHops(msgHash, srcChainId, destChainId); + } else { + return await this._encodedSignalProofWithoutHops(msgHash, srcChainId, destChainId); + } } // Reference: EncodedSignalProof in relayer/proof/encoded_signal_proof.go // protocol/contracts/signal/SignalService.sol - private _encodedSignalProof(crossChainSyncAddress: Address, rlpEncodedStorageProof: Hex, blockHeight: bigint) { - type Hop = { - signalRootRelay: Address; - signalRoot: Hex; - storageProof: Hex; - }; + async _encodedSignalProofWithoutHops(msgHash: Hash, srcChainId: number, destChainId: number) { + const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; + const srcSignalServiceAddress = routingContractsMap[srcChainId][destChainId].signalServiceAddress; + const destCrossChainSyncAddress = routingContractsMap[destChainId][srcChainId].crossChainSyncAddress; + + // Get the block from chain A based on the latest block hash + // we get cross chain (Taiko contract on chain B) + const blockNumber = await this.getBlockNumber(srcChainId, destChainId, destCrossChainSyncAddress); + + const { rlpEncodedStorageProof } = await this.encodedStorageProof({ + msgHash, + clientChainId: srcChainId, + contractAddress: srcBridgeAddress, + proofForAccountAddress: srcSignalServiceAddress, + blockNumber, + }); + + const signalProof = this._encodeAbiParameters( + destCrossChainSyncAddress, + BigInt(blockNumber), + rlpEncodedStorageProof, + [], + ); + return signalProof; + } + + async _encodedSignalProofWithHops(msgHash: Hash, srcChainId: number, destChainId: number) { + const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; + const srcSignalServiceAddress = routingContractsMap[srcChainId][destChainId].signalServiceAddress; + const destCrossChainSyncAddress = routingContractsMap[destChainId][srcChainId].crossChainSyncAddress; + const hopParams = routingContractsMap[srcChainId][destChainId].hops; + if (hopParams === undefined) throw new WrongBridgeConfigError('hops is undefined'); + + let blockNumber = BigInt(0); + // Initialize hopChainId with src chain + let hopChainId: number = srcChainId; + for (const hop of hopParams) { + blockNumber = await this.getBlockNumber(hopChainId, hop.chainId, hop.crossChainSyncAddress); + hopChainId = hop.chainId; + } + // Get the block number from last hop chain to dest chain + blockNumber = await this.getBlockNumber(hopChainId, destChainId, destCrossChainSyncAddress); + + // Generate main storage proof with receipt.blockNumber + const { proof, rlpEncodedStorageProof } = await this.encodedStorageProof({ + msgHash, + clientChainId: srcChainId, + contractAddress: srcBridgeAddress, + proofForAccountAddress: srcSignalServiceAddress, + blockNumber, + }); + // The first signalRoot + let signalRoot = proof.storageHash; + log('successfully generated main storage proof', signalRoot); const hops: Hop[] = []; - const signalProof = encodeAbiParameters( + for (const hop of hopParams) { + const { proof: hopProof, rlpEncodedStorageProof: hopRlpEncodedStorageProof } = await this.encodedStorageProof({ + msgHash: signalRoot, + clientChainId: hop.chainId, + contractAddress: hop.crossChainSyncAddress, + proofForAccountAddress: hop.signalServiceAddress, + blockNumber, + }); + log('successfully generated hop storage proof', hopProof.storageHash); + + hops.push({ + signalRootRelay: hop.crossChainSyncAddress, + signalRoot: signalRoot, + storageProof: hopRlpEncodedStorageProof, + }); + signalRoot = hopProof.storageHash; + } + + const signalProof = this._encodeAbiParameters( + destCrossChainSyncAddress, + BigInt(blockNumber), + rlpEncodedStorageProof, + hops, + ); + return signalProof; + } + + async generateProofToRelease(msgHash: Hash, srcChainId: number, destChainId: number) { + const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; + const destBridgeAddress = routingContractsMap[destChainId][srcChainId].bridgeAddress; + const destCrossChainSyncAddress = routingContractsMap[destChainId][srcChainId].crossChainSyncAddress; + + const blockNumber = await this.getBlockNumber(srcChainId, destChainId, destCrossChainSyncAddress); + + const { proof } = await this.encodedStorageProof({ + msgHash, + clientChainId: destChainId, + contractAddress: srcBridgeAddress, + proofForAccountAddress: destBridgeAddress, + blockNumber, + }); + + // Value must be 0x3 => MessageStatus.FAILED + if (proof.storageProof[0].value !== toHex(MessageStatus.FAILED)) { + throw new InvalidProofError('storage proof value is not FAILED'); + } + + return this._encodedSignalProofWithoutHops(msgHash, destChainId, srcChainId); + } + + _encodeAbiParameters(crossChainSync: Address, height: bigint, storageProof: Hex, hops: Hop[]) { + return encodeAbiParameters( [ { type: 'tuple', @@ -52,51 +234,12 @@ export class BridgeProver extends Prover { ], [ { - crossChainSync: crossChainSyncAddress, - height: blockHeight, - storageProof: rlpEncodedStorageProof, - hops: hops, + crossChainSync, + height, + storageProof, + hops, }, ], ); - - return signalProof; - } - - async encodedSignalProof(msgHash: Hash, srcChainId: number, destChainId: number) { - const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; - const srcSignalServiceAddress = routingContractsMap[srcChainId][destChainId].signalServiceAddress; - const destCrossChainSyncAddress = routingContractsMap[destChainId][srcChainId].crossChainSyncAddress; - - const { rlpEncodedStorageProof, block } = await this.encodedStorageProof({ - msgHash, - clientChainId: srcChainId, - contractAddress: srcBridgeAddress, - crossChainSyncChainId: destChainId, - proofForAccountAddress: srcSignalServiceAddress, - }); - - return this._encodedSignalProof(destCrossChainSyncAddress, rlpEncodedStorageProof, block.number as bigint); - } - - async generateProofToRelease(msgHash: Hash, srcChainId: number, destChainId: number) { - const srcBridgeAddress = routingContractsMap[srcChainId][destChainId].bridgeAddress; - const destBridgeAddress = routingContractsMap[destChainId][srcChainId].bridgeAddress; - const srcCrossChainSyncAddress = routingContractsMap[srcChainId][destChainId].crossChainSyncAddress; - - const { proof, rlpEncodedStorageProof, block } = await this.encodedStorageProof({ - msgHash, - clientChainId: destChainId, - contractAddress: srcBridgeAddress, - crossChainSyncChainId: srcChainId, - proofForAccountAddress: destBridgeAddress, - }); - - // Value must be 0x3 => MessageStatus.FAILED - if (proof.storageProof[0].value !== toHex(MessageStatus.FAILED)) { - throw new InvalidProofError('storage proof value is not FAILED'); - } - - return this._encodedSignalProof(srcCrossChainSyncAddress, rlpEncodedStorageProof, block.number as bigint); } } diff --git a/packages/bridge-ui-v2/src/libs/proof/Prover.ts b/packages/bridge-ui-v2/src/libs/proof/Prover.ts deleted file mode 100644 index ac3c109b5ef..00000000000 --- a/packages/bridge-ui-v2/src/libs/proof/Prover.ts +++ /dev/null @@ -1,83 +0,0 @@ -import { getContract, type GetContractResult, type PublicClient } from '@wagmi/core'; -import { type Address, encodePacked, type Hex, keccak256, toHex, toRlp } from 'viem'; - -import { crossChainSyncABI } from '$abi'; -import { routingContractsMap } from '$bridgeConfig'; -import { InvalidProofError, PendingBlockError } from '$libs/error'; -import { getLogger } from '$libs/util/logger'; -import { publicClient } from '$libs/wagmi'; - -import type { ClientWithEthGetProofRequest, GenerateProofArgs } from './types'; - -const log = getLogger('proof:Prover'); - -export class Prover { - async getSignalSlot(chainId: number, contractAddress: Address, msgHash: Hex) { - return keccak256( - encodePacked(['string', 'uint64', 'address', 'bytes32'], ['SIGNAL', BigInt(chainId), contractAddress, msgHash]), - ); - } - - protected async getLatestBlockFromGetSyncedSnippet( - client: PublicClient, - crossChainSyncContract: GetContractResult, - ) { - const syncedSnippet = await crossChainSyncContract.read.getSyncedSnippet([BigInt(0)]); - const latestBlockHash = syncedSnippet['blockHash']; - return client.getBlock({ blockHash: latestBlockHash }); - } - - async encodedStorageProof(args: GenerateProofArgs) { - const { msgHash, clientChainId, contractAddress, crossChainSyncChainId, proofForAccountAddress } = args; - - const crossChainSyncAddress = routingContractsMap[crossChainSyncChainId][clientChainId].crossChainSyncAddress; - - // Get the block from chain A based on the latest block hash - // we get cross chain (Taiko contract on chain B) - const crossChainSyncContract = getContract({ - chainId: crossChainSyncChainId, - address: crossChainSyncAddress, - abi: crossChainSyncABI, - }); - - const client = publicClient({ chainId: clientChainId }); - const block = await this.getLatestBlockFromGetSyncedSnippet(client, crossChainSyncContract); - - if (block.hash === null || block.number === null) { - throw new PendingBlockError('block is pending'); - } - - const key = await this.getSignalSlot(clientChainId, contractAddress, msgHash); - - // Unfortunately, since this method is stagnant, it hasn't been included into Viem lib - // as supported methods. Still stupported by Alchmey, Infura and others. - // See https://eips.ethereum.org/EIPS/eip-1186 - // Following is a workaround to support this method. - const clientWithEthProofRequest = client as ClientWithEthGetProofRequest; - - // RPC call to get the merkle proof what value is at key on the SignalService contract - const proof = await clientWithEthProofRequest.request({ - method: 'eth_getProof', - params: [ - // Address of the account to get the proof for - proofForAccountAddress, - - // Array of storage-keys that should be proofed and included - [key], - - block.hash, - ], - }); - - log('Proof from eth_getProof', proof); - - if (proof.storageProof[0].value !== toHex(true)) { - throw new InvalidProofError('storage proof value is not 1'); - } - - // RLP encode the proof together for LibTrieProof to decode - const rlpEncodedStorageProof = toRlp(proof.storageProof[0].proof); - - return { proof, rlpEncodedStorageProof, block }; - } -} diff --git a/packages/bridge-ui-v2/src/libs/proof/index.ts b/packages/bridge-ui-v2/src/libs/proof/index.ts index ee1dc6d890a..7e6118e765d 100644 --- a/packages/bridge-ui-v2/src/libs/proof/index.ts +++ b/packages/bridge-ui-v2/src/libs/proof/index.ts @@ -1,2 +1 @@ export { BridgeProver } from './BridgeProver'; -export { Prover } from './Prover'; diff --git a/packages/bridge-ui-v2/src/libs/proof/types.ts b/packages/bridge-ui-v2/src/libs/proof/types.ts index 6d39cd737c9..7bb722f701f 100644 --- a/packages/bridge-ui-v2/src/libs/proof/types.ts +++ b/packages/bridge-ui-v2/src/libs/proof/types.ts @@ -4,8 +4,8 @@ export type GenerateProofArgs = { msgHash: Hash; contractAddress: Address; proofForAccountAddress: Address; - crossChainSyncChainId: number; clientChainId: number; + blockNumber: bigint; }; export type StorageEntry = { @@ -16,6 +16,12 @@ export type StorageEntry = { proof: Hex[]; }; +export type Hop = { + signalRootRelay: Address; + signalRoot: Hex; + storageProof: Hex; +}; + export type EthGetProofResponse = { balance: bigint; codeHash: Hash;