From f45d8175fc951d6707da0bbc94222a77443baeb1 Mon Sep 17 00:00:00 2001 From: Shadab Khan Date: Tue, 14 Feb 2023 19:06:04 +0530 Subject: [PATCH 01/23] feat(bridge-ui): adapt frontend to backend changes --- packages/bridge-ui/README.md | 24 +++--- .../components/MessageStatusTooltip.svelte | 8 +- .../src/components/form/SelectChain.svelte | 2 +- .../src/components/modals/FaucetModal.svelte | 16 ++-- packages/bridge-ui/src/domain/chain.ts | 83 ++++++++++++++----- packages/bridge-ui/src/domain/proof.ts | 1 + packages/bridge-ui/src/domain/token.ts | 10 +-- packages/bridge-ui/src/erc20/bridge.ts | 1 + packages/bridge-ui/src/eth/bridge.ts | 1 + packages/bridge-ui/src/proof/service.ts | 2 +- packages/bridge-ui/src/storage/service.ts | 7 +- .../src/utils/recommendProcessingFee.spec.ts | 6 +- 12 files changed, 108 insertions(+), 53 deletions(-) diff --git a/packages/bridge-ui/README.md b/packages/bridge-ui/README.md index 59cd6eb5f5..40678322b5 100644 --- a/packages/bridge-ui/README.md +++ b/packages/bridge-ui/README.md @@ -14,18 +14,20 @@ You can use the following values in the `.env` file to spin up the Bridge UI loc ``` VITE_NODE_ENV=dev -VITE_L1_RPC_URL="https://l1rpc.a1.taiko.xyz/" -VITE_L2_RPC_URL="https://l2rpc.a1.taiko.xyz/" -VITE_RELAYER_URL="http://localhost:4102/" -VITE_TEST_ERC20_ADDRESS_MAINNET="0x3E3a3416DbCc5cb4448B6b171fF15f9Da35Ab72d" -VITE_MAINNET_CHAIN_ID=31338 -VITE_TAIKO_CHAIN_ID=167003 -VITE_MAINNET_CHAIN_NAME="Ethereum A1" -VITE_TAIKO_CHAIN_NAME="Taiko A1" +VITE_L1_RPC_URL="https://l1rpc.internal.taiko.xyz/" +VITE_L2_RPC_URL="https://l2rpc.internal.taiko.xyz/" +VITE_RELAYER_URL="https://relayer.internal.taiko.xyz/" +VITE_TEST_ERC20_ADDRESS_MAINNET="0x1B5Ccd66cc2408A0084047720167F6234Dc5498A" +VITE_TEST_ERC20_SYMBOL_MAINNET="BULL" +VITE_TEST_ERC20_NAME_MAINNET="Bull Token" +VITE_MAINNET_CHAIN_ID=31336 +VITE_TAIKO_CHAIN_ID=167001 +VITE_MAINNET_CHAIN_NAME="Ethereum A2" +VITE_TAIKO_CHAIN_NAME="Taiko A2" VITE_TAIKO_TOKEN_VAULT_ADDRESS="0x0000777700000000000000000000000000000002" -VITE_MAINNET_TOKEN_VAULT_ADDRESS="0xD0dfd5baCf160B97C8eE3ecb463F18c08673160c" +VITE_MAINNET_TOKEN_VAULT_ADDRESS="0xAE4C9bD0f7AE5398Df05043079596E2BF0079CE9" VITE_TAIKO_HEADER_SYNC_ADDRESS="0x0000777700000000000000000000000000000001" -VITE_MAINNET_HEADER_SYNC_ADDRESS="0x7B3AF414448ba906f02a1CA307C56c4ADFF27ce7" -VITE_MAINNET_BRIDGE_ADDRESS="0x3612E284D763f42f5E4CB72B1602b23DAEC3cA60" +VITE_MAINNET_HEADER_SYNC_ADDRESS="0xa6421A7f48498cee3aEb6428a8A2DD5fAA3AcE2f" +VITE_MAINNET_BRIDGE_ADDRESS="0x0237443359aB0b11EcDC41A7aF1C90226a88c70f" VITE_TAIKO_BRIDGE_ADDRESS="0x0000777700000000000000000000000000000004" ``` \ No newline at end of file diff --git a/packages/bridge-ui/src/components/MessageStatusTooltip.svelte b/packages/bridge-ui/src/components/MessageStatusTooltip.svelte index 2aca9cde74..f24fc14312 100644 --- a/packages/bridge-ui/src/components/MessageStatusTooltip.svelte +++ b/packages/bridge-ui/src/components/MessageStatusTooltip.svelte @@ -11,8 +11,12 @@ diff --git a/packages/bridge-ui/src/components/Transaction.svelte b/packages/bridge-ui/src/components/Transaction.svelte index cb92b02533..6bc7fe6bbe 100644 --- a/packages/bridge-ui/src/components/Transaction.svelte +++ b/packages/bridge-ui/src/components/Transaction.svelte @@ -202,14 +202,14 @@ {:else if transaction.receipt && transaction.status === MessageStatus.New} {:else if transaction.status === MessageStatus.Retriable} {:else if transaction.status === MessageStatus.Done} Claimed From b09fa90f4ffd40e0333a10cec2784309071a8b04 Mon Sep 17 00:00:00 2001 From: Shadab Khan Date: Wed, 22 Feb 2023 23:39:15 +0530 Subject: [PATCH 19/23] fix(bridge-ui): fix tests --- packages/bridge-ui/src/erc20/bridge.spec.ts | 1 + packages/bridge-ui/src/eth/bridge.spec.ts | 1 + 2 files changed, 2 insertions(+) diff --git a/packages/bridge-ui/src/erc20/bridge.spec.ts b/packages/bridge-ui/src/erc20/bridge.spec.ts index af65843a78..acd55b12b0 100644 --- a/packages/bridge-ui/src/erc20/bridge.spec.ts +++ b/packages/bridge-ui/src/erc20/bridge.spec.ts @@ -24,6 +24,7 @@ const mockContract = { const mockProver = { GenerateProof: jest.fn(), + GenerateReleaseProof: jest.fn(), }; jest.mock("ethers", () => ({ diff --git a/packages/bridge-ui/src/eth/bridge.spec.ts b/packages/bridge-ui/src/eth/bridge.spec.ts index 458f9155e4..952e298a15 100644 --- a/packages/bridge-ui/src/eth/bridge.spec.ts +++ b/packages/bridge-ui/src/eth/bridge.spec.ts @@ -22,6 +22,7 @@ const mockContract = { const mockProver = { GenerateProof: jest.fn(), + GenerateReleaseProof: jest.fn(), }; jest.mock("ethers", () => ({ From 768046621c541ead0c35d95f2df444493a2487b9 Mon Sep 17 00:00:00 2001 From: Shadab Khan Date: Thu, 23 Feb 2023 11:47:30 +0530 Subject: [PATCH 20/23] chore(bridge-ui): tests --- packages/bridge-ui/jest.config.js | 2 +- packages/bridge-ui/src/erc20/bridge.spec.ts | 75 +++++++++++- packages/bridge-ui/src/eth/bridge.spec.ts | 78 +++++++++++- packages/bridge-ui/src/eth/bridge.ts | 14 +-- packages/bridge-ui/src/proof/service.spec.ts | 121 +++++++++++++++++++ 5 files changed, 280 insertions(+), 10 deletions(-) diff --git a/packages/bridge-ui/jest.config.js b/packages/bridge-ui/jest.config.js index e7adeb038d..8a0c0adcfe 100644 --- a/packages/bridge-ui/jest.config.js +++ b/packages/bridge-ui/jest.config.js @@ -42,7 +42,7 @@ export default { statements: 95, branches: 72, functions: 89, - lines: 96, + lines: 95, }, }, modulePathIgnorePatterns: ["/public/build/"], diff --git a/packages/bridge-ui/src/erc20/bridge.spec.ts b/packages/bridge-ui/src/erc20/bridge.spec.ts index acd55b12b0..a01aa648a5 100644 --- a/packages/bridge-ui/src/erc20/bridge.spec.ts +++ b/packages/bridge-ui/src/erc20/bridge.spec.ts @@ -1,4 +1,4 @@ -import { BigNumber, Wallet } from "ethers"; +import { BigNumber, ethers, Wallet } from "ethers"; import { CHAIN_ID_MAINNET, CHAIN_ID_TAIKO, @@ -20,6 +20,7 @@ const mockContract = { processMessage: jest.fn(), retryMessage: jest.fn(), getMessageStatus: jest.fn(), + releaseERC20: jest.fn(), }; const mockProver = { @@ -396,4 +397,76 @@ describe("bridge tests", () => { expect(mockContract.retryMessage).toHaveBeenCalled(); }); + + it("release tokens throws if message is already in DONE status", async () => { + mockContract.getMessageStatus.mockImplementationOnce(() => { + return MessageStatus.Done; + }); + + mockSigner.getAddress.mockImplementationOnce(() => { + return "0x"; + }); + + const wallet = new Wallet("0x"); + + const bridge: Bridge = new ERC20Bridge(mockProver); + + expect(mockContract.releaseERC20).not.toHaveBeenCalled(); + + expect(mockProver.GenerateReleaseProof).not.toHaveBeenCalled(); + + await expect(bridge.ReleaseTokens({ + message: { + owner: "0x", + srcChainId: BigNumber.from(CHAIN_ID_TAIKO), + destChainId: BigNumber.from(CHAIN_ID_MAINNET), + sender: "0x01", + gasLimit: BigNumber.from(1), + } as unknown as Message, + msgHash: "0x", + srcBridgeAddress: "0x", + destBridgeAddress: "0x", + signer: wallet, + destProvider: new ethers.providers.JsonRpcProvider(), + srcTokenVaultAddress: "0x" + })).rejects.toThrowError("message already processed"); + }); + + it("release tokens", async () => { + mockContract.getMessageStatus.mockImplementationOnce(() => { + return MessageStatus.Failed; + }); + + mockSigner.getAddress.mockImplementationOnce(() => { + return "0x"; + }); + + const wallet = new Wallet("0x"); + + const bridge: Bridge = new ERC20Bridge(mockProver); + + expect(mockContract.releaseERC20).not.toHaveBeenCalled(); + + expect(mockProver.GenerateReleaseProof).not.toHaveBeenCalled(); + + await bridge.ReleaseTokens({ + message: { + owner: "0x", + srcChainId: BigNumber.from(CHAIN_ID_TAIKO), + destChainId: BigNumber.from(CHAIN_ID_MAINNET), + sender: "0x01", + gasLimit: BigNumber.from(1), + } as unknown as Message, + msgHash: "0x", + srcBridgeAddress: "0x", + destBridgeAddress: "0x", + signer: wallet, + destProvider: new ethers.providers.JsonRpcProvider(), + srcTokenVaultAddress: "0x" + }); + + expect(mockProver.GenerateReleaseProof).toHaveBeenCalled(); + + expect(mockContract.releaseERC20).toHaveBeenCalled(); + }); }); diff --git a/packages/bridge-ui/src/eth/bridge.spec.ts b/packages/bridge-ui/src/eth/bridge.spec.ts index 952e298a15..470a7ae9e9 100644 --- a/packages/bridge-ui/src/eth/bridge.spec.ts +++ b/packages/bridge-ui/src/eth/bridge.spec.ts @@ -1,4 +1,4 @@ -import { BigNumber, Wallet } from "ethers"; +import { BigNumber, ethers, Wallet } from "ethers"; import { CHAIN_ID_MAINNET, CHAIN_ID_TAIKO, @@ -18,6 +18,7 @@ const mockContract = { getMessageStatus: jest.fn(), processMessage: jest.fn(), retryMessage: jest.fn(), + releaseEther: jest.fn(), }; const mockProver = { @@ -37,6 +38,9 @@ jest.mock("ethers", () => ({ Contract: function () { return mockContract; }, + providers: { + JsonRpcProvider: jest.fn() + } })); describe("bridge tests", () => { @@ -251,4 +255,76 @@ describe("bridge tests", () => { expect(mockContract.retryMessage).toHaveBeenCalled(); }); + + it("release tokens throws if message is already in DONE status", async () => { + mockContract.getMessageStatus.mockImplementationOnce(() => { + return MessageStatus.Done; + }); + + mockSigner.getAddress.mockImplementationOnce(() => { + return "0x"; + }); + + const wallet = new Wallet("0x"); + + const bridge: Bridge = new ETHBridge(mockProver); + + expect(mockContract.releaseEther).not.toHaveBeenCalled(); + + expect(mockProver.GenerateReleaseProof).not.toHaveBeenCalled(); + + await expect(bridge.ReleaseTokens({ + message: { + owner: "0x", + srcChainId: BigNumber.from(CHAIN_ID_TAIKO), + destChainId: BigNumber.from(CHAIN_ID_MAINNET), + sender: "0x01", + gasLimit: BigNumber.from(1), + } as unknown as Message, + msgHash: "0x", + srcBridgeAddress: "0x", + destBridgeAddress: "0x", + signer: wallet, + destProvider: new ethers.providers.JsonRpcProvider(), + srcTokenVaultAddress: "0x" + })).rejects.toThrowError("message already processed"); + }); + + it("release tokens", async () => { + mockContract.getMessageStatus.mockImplementationOnce(() => { + return MessageStatus.Failed; + }); + + mockSigner.getAddress.mockImplementationOnce(() => { + return "0x"; + }); + + const wallet = new Wallet("0x"); + + const bridge: Bridge = new ETHBridge(mockProver); + + expect(mockContract.releaseEther).not.toHaveBeenCalled(); + + expect(mockProver.GenerateReleaseProof).not.toHaveBeenCalled(); + + await bridge.ReleaseTokens({ + message: { + owner: "0x", + srcChainId: BigNumber.from(CHAIN_ID_TAIKO), + destChainId: BigNumber.from(CHAIN_ID_MAINNET), + sender: "0x01", + gasLimit: BigNumber.from(1), + } as unknown as Message, + msgHash: "0x", + srcBridgeAddress: "0x", + destBridgeAddress: "0x", + signer: wallet, + destProvider: new ethers.providers.JsonRpcProvider(), + srcTokenVaultAddress: "0x" + }); + + expect(mockProver.GenerateReleaseProof).toHaveBeenCalled(); + + expect(mockContract.releaseEther).toHaveBeenCalled(); + }); }); diff --git a/packages/bridge-ui/src/eth/bridge.ts b/packages/bridge-ui/src/eth/bridge.ts index eeeee6ebcd..c73f6b5076 100644 --- a/packages/bridge-ui/src/eth/bridge.ts +++ b/packages/bridge-ui/src/eth/bridge.ts @@ -150,13 +150,13 @@ class ETHBridge implements BridgeInterface { opts.destProvider ); - const messageStatus: MessageStatus = await destBridgeContract.getMessageStatus( - opts.msgHash - ); - - if (messageStatus === MessageStatus.Done) { - throw Error("message already processed"); - } + const messageStatus: MessageStatus = await destBridgeContract.getMessageStatus( + opts.msgHash + ); + + if (messageStatus === MessageStatus.Done) { + throw Error("message already processed"); + } const signerAddress = await opts.signer.getAddress(); diff --git a/packages/bridge-ui/src/proof/service.spec.ts b/packages/bridge-ui/src/proof/service.spec.ts index 0686380ee6..f9003a2f7f 100644 --- a/packages/bridge-ui/src/proof/service.spec.ts +++ b/packages/bridge-ui/src/proof/service.spec.ts @@ -73,6 +73,36 @@ const invalidStorageProof: EthGetProofResponse = { ], }; +const storageProof2: EthGetProofResponse = { + balance: "", + nonce: "", + codeHash: "", + storageHash: "", + accountProof: [], + storageProof: [ + { + key: "0x01", + value: "0x3", + proof: [ethers.constants.HashZero], + }, + ], +}; + +const invalidStorageProof2: EthGetProofResponse = { + balance: "", + nonce: "", + codeHash: "", + storageHash: "", + accountProof: [], + storageProof: [ + { + key: "0x01", + value: "0x0", + proof: [ethers.constants.HashZero], + }, + ], +}; + const expectedProof = "0x000000000000000000000000000000000000000000000000000000000000002000000000000000000000000000000000000000000000000000000000000000400000000000000000000000000000000000000000000000000000000000000360a7881266ca0a344c43cb24175d9dbd243b58d45d6ae6ad71310a273a3d1d3afb1dcc4de8dec75d7aab85b567b6ccd41ad312451b948a7413f0a142fd40d49347000000000000000000000000ea674fdde714fd979de3edf0f56aa9716b898ec8c0dcf937b3f6136dd70a1ad11cc57b040fd410f3c49a5146f20c732895a3cc217273ade6b6ed865a9975ac281da23b90b141a8b607d874d2cd95e65e81336f8e74bb61e381e9238a08b169580f3cbf9b8b79d7d5ee708d3e286103eb291dfd0800000000000400000000000000000000000000400000000000000000000000000000000000000000000000000000000000000000000000000000000000000100000000000000000000000000000000000000000000000000000000000000000000000000000000000000000020000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000004000000000000000000000000020000000000000000000000000000000000000000000000000100000000008000000000000000000000000000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b000000000000000000000000000000000000000000000000000000000000007b00000000000000000000000000000000000000000000000000000000000002e0f5ba25df1e92e89a09e0b32063b81795f631100801158f5fa733f2ba26843bd0000000000000000000000000000000000000000000000000000000000000007b0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000001265746865726d696e652d75732d7765737431000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000e0000000000000000000000000000000000000000000000000000000000000004000000000000000000000000000000000000000000000000000000000000000800000000000000000000000000000000000000000000000000000000000000001c0000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000022e1a00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000"; @@ -176,3 +206,94 @@ describe("prover tests", () => { expect(proof).toBe(expectedProofWithBaseFee); }); }); + +describe("generate release proof tests", () => { + beforeEach(() => { + jest.resetAllMocks(); + block.baseFeePerGas = "0"; + }); + + it("throws on invalid proof", async () => { + mockProvider.send.mockImplementation( + (method: string, params: unknown[]) => { + if (method === "eth_getBlockByHash") { + return block; + } + + if (method === "eth_getProof") { + return invalidStorageProof2; + } + } + ); + + const prover: ProofService = new ProofService(map); + + await expect( + prover.GenerateReleaseProof({ + msgHash: ethers.constants.HashZero, + sender: ethers.constants.AddressZero, + destBridgeAddress: ethers.constants.AddressZero, + srcChain: srcChain, + destChain: destChain, + destHeaderSyncAddress: ethers.constants.AddressZero, + srcHeaderSyncAddress: ethers.constants.AddressZero, + }) + ).rejects.toThrowError("invalid proof"); + }); + + it("generates proof", async () => { + mockProvider.send.mockImplementation( + (method: string, params: unknown[]) => { + if (method === "eth_getBlockByHash") { + return block; + } + + if (method === "eth_getProof") { + return storageProof2; + } + } + ); + + const prover: ProofService = new ProofService(map); + + const proof = await prover.GenerateReleaseProof({ + msgHash: ethers.constants.HashZero, + sender: ethers.constants.AddressZero, + destBridgeAddress: ethers.constants.AddressZero, + srcChain: srcChain, + destChain: destChain, + destHeaderSyncAddress: ethers.constants.AddressZero, + srcHeaderSyncAddress: ethers.constants.AddressZero, + }); + expect(proof).toBe(expectedProof); + }); + + it("generates proof with baseFeePerGas set", async () => { + mockProvider.send.mockImplementation( + (method: string, params: unknown[]) => { + if (method === "eth_getBlockByHash") { + return block; + } + + if (method === "eth_getProof") { + return storageProof2; + } + } + ); + + block.baseFeePerGas = "1"; + + const prover: ProofService = new ProofService(map); + + const proof = await prover.GenerateReleaseProof({ + msgHash: ethers.constants.HashZero, + sender: ethers.constants.AddressZero, + destBridgeAddress: ethers.constants.AddressZero, + srcChain: srcChain, + destChain: destChain, + destHeaderSyncAddress: ethers.constants.AddressZero, + srcHeaderSyncAddress: ethers.constants.AddressZero, + }); + expect(proof).toBe(expectedProofWithBaseFee); + }); +}); From 7be1ac22719e261d7d472e14a10b9db5f5f84c0a Mon Sep 17 00:00:00 2001 From: Shadab Khan Date: Thu, 23 Feb 2023 15:15:50 +0530 Subject: [PATCH 21/23] chore(bridge-ui): refactor duplicate code --- .../src/components/Transaction.svelte | 117 ++++++++---------- packages/bridge-ui/src/erc20/bridge.ts | 1 - packages/bridge-ui/src/eth/bridge.ts | 3 +- packages/bridge-ui/src/proof/service.ts | 105 ++++++---------- 4 files changed, 92 insertions(+), 134 deletions(-) diff --git a/packages/bridge-ui/src/components/Transaction.svelte b/packages/bridge-ui/src/components/Transaction.svelte index 6bc7fe6bbe..0f6f8b1401 100644 --- a/packages/bridge-ui/src/components/Transaction.svelte +++ b/packages/bridge-ui/src/components/Transaction.svelte @@ -39,26 +39,30 @@ processable = await isProcessable(); }); + async function switchChainAndSetSigner(chain: Chain) { + await switchNetwork({ + chainId: chain.id, + }); + const provider = new ethers.providers.Web3Provider(window.ethereum); + await provider.send("eth_requestAccounts", []); + + fromChainStore.set(chain); + if (chain === CHAIN_MAINNET) { + toChainStore.set(CHAIN_TKO); + } else { + toChainStore.set(CHAIN_MAINNET); + } + const wagmiSigner = await fetchSigner(); + signer.set(wagmiSigner); + } + async function claim(bridgeTx: BridgeTransaction) { try { - loading = true; - if (fromChain.id !== bridgeTx.message.destChainId.toNumber()) { - const chain = chains[bridgeTx.message.destChainId.toNumber()]; - await switchNetwork({ - chainId: chain.id, - }); - const provider = new ethers.providers.Web3Provider(window.ethereum); - await provider.send("eth_requestAccounts", []); - - fromChainStore.set(chain); - if (chain === CHAIN_MAINNET) { - toChainStore.set(CHAIN_TKO); - } else { - toChainStore.set(CHAIN_MAINNET); + loading = true; + if (fromChain.id !== bridgeTx.message.destChainId.toNumber()) { + const chain = chains[bridgeTx.message.destChainId.toNumber()]; + await switchChainAndSetSigner(chain) } - const wagmiSigner = await fetchSigner(); - signer.set(wagmiSigner); - } const tx = await $bridges .get(bridgeTx.message.data === "0x" ? BridgeType.ETH : BridgeType.ERC20) .Claim({ @@ -85,57 +89,13 @@ } } - async function isProcessable() { - if (!transaction.receipt) return false; - if (!transaction.message) return false; - if (transaction.status !== MessageStatus.New) return true; - - const contract = new Contract( - chains[transaction.message.destChainId.toNumber()].headerSyncAddress, - HeaderSync, - $providers.get(chains[transaction.message.destChainId.toNumber()].id) - ); - - const latestSyncedHeader = await contract.getLatestSyncedHeader(); - const srcBlock = await $providers - .get(chains[transaction.message.srcChainId.toNumber()].id) - .getBlock(latestSyncedHeader); - return transaction.receipt.blockNumber <= srcBlock.number; - } - - const interval = setInterval(async () => { - processable = await isProcessable(); - const contract = new ethers.Contract( - chains[transaction.toChainId].bridgeAddress, - Bridge, - $providers.get(chains[transaction.message.destChainId.toNumber()].id) - ); - - transaction.status = await contract.getMessageStatus(transaction.msgHash); - transaction = transaction; - if (transaction.status === MessageStatus.Done) clearInterval(interval); - }, 20 * 1000); - async function releaseTokens(bridgeTx: BridgeTransaction) { try { loading = true; if (fromChain.id !== bridgeTx.message.srcChainId.toNumber()) { - const chain = chains[bridgeTx.message.srcChainId.toNumber()]; - await switchNetwork({ - chainId: chain.id, - }); - const provider = new ethers.providers.Web3Provider(window.ethereum); - await provider.send("eth_requestAccounts", []); - - fromChainStore.set(chain); - if (chain === CHAIN_MAINNET) { - toChainStore.set(CHAIN_TKO); - } else { - toChainStore.set(CHAIN_MAINNET); + const chain = chains[bridgeTx.message.srcChainId.toNumber()]; + await switchChainAndSetSigner(chain) } - const wagmiSigner = await fetchSigner(); - signer.set(wagmiSigner); - } const tx = await $bridges .get(bridgeTx.message.data === "0x" ? BridgeType.ETH : BridgeType.ERC20) .ReleaseTokens({ @@ -163,6 +123,37 @@ loading = false; } } + + async function isProcessable() { + if (!transaction.receipt) return false; + if (!transaction.message) return false; + if (transaction.status !== MessageStatus.New) return true; + + const contract = new Contract( + chains[transaction.message.destChainId.toNumber()].headerSyncAddress, + HeaderSync, + $providers.get(chains[transaction.message.destChainId.toNumber()].id) + ); + + const latestSyncedHeader = await contract.getLatestSyncedHeader(); + const srcBlock = await $providers + .get(chains[transaction.message.srcChainId.toNumber()].id) + .getBlock(latestSyncedHeader); + return transaction.receipt.blockNumber <= srcBlock.number; + } + + const interval = setInterval(async () => { + processable = await isProcessable(); + const contract = new ethers.Contract( + chains[transaction.toChainId].bridgeAddress, + Bridge, + $providers.get(chains[transaction.message.destChainId.toNumber()].id) + ); + + transaction.status = await contract.getMessageStatus(transaction.msgHash); + transaction = transaction; + if (transaction.status === MessageStatus.Done) clearInterval(interval); + }, 20 * 1000); diff --git a/packages/bridge-ui/src/erc20/bridge.ts b/packages/bridge-ui/src/erc20/bridge.ts index 9be8c7bea2..0622f4afdf 100644 --- a/packages/bridge-ui/src/erc20/bridge.ts +++ b/packages/bridge-ui/src/erc20/bridge.ts @@ -232,7 +232,6 @@ class ERC20Bridge implements Bridge { }; const proof = await this.prover.GenerateReleaseProof(proofOpts); - console.log(proof); const srcTokenVaultContract: Contract = new Contract( opts.srcTokenVaultAddress, diff --git a/packages/bridge-ui/src/eth/bridge.ts b/packages/bridge-ui/src/eth/bridge.ts index c73f6b5076..e8f3152e8e 100644 --- a/packages/bridge-ui/src/eth/bridge.ts +++ b/packages/bridge-ui/src/eth/bridge.ts @@ -139,7 +139,7 @@ class ETHBridge implements BridgeInterface { return await contract.processMessage(opts.message, proof); } else { - return await contract.retryMessage(opts.message); + return await contract.retryMessage(opts.message, true); } } @@ -178,7 +178,6 @@ class ETHBridge implements BridgeInterface { }; const proof = await this.prover.GenerateReleaseProof(proofOpts); - console.log(proof); const srcBridgeContract: Contract = new Contract( opts.srcBridgeAddress, diff --git a/packages/bridge-ui/src/proof/service.ts b/packages/bridge-ui/src/proof/service.ts index 50257aa444..cf635bf8f1 100644 --- a/packages/bridge-ui/src/proof/service.ts +++ b/packages/bridge-ui/src/proof/service.ts @@ -17,7 +17,7 @@ class ProofService implements Prover { this.providerMap = providerMap; } - async GenerateProof(opts: GenerateProofOpts): Promise { + private static getKey(opts: GenerateProofOpts | GenerateReleaseProofOpts) { const key = ethers.utils.keccak256( ethers.utils.solidityPack( ["address", "bytes32"], @@ -25,14 +25,10 @@ class ProofService implements Prover { ) ); - const provider = this.providerMap.get(opts.srcChain); - - const contract = new Contract( - opts.destHeaderSyncAddress, - HeaderSync, - this.providerMap.get(opts.destChain) - ); + return key; + } + private static async getBlockAndBlockHeader(contract: ethers.Contract, provider: ethers.providers.JsonRpcProvider): Promise<{block: Block, blockHeader: BlockHeader}> { const latestSyncedHeader = await contract.getLatestSyncedHeader(); const block: Block = await provider.send("eth_getBlockByHash", [ @@ -61,17 +57,10 @@ class ProofService implements Prover { baseFeePerGas: block.baseFeePerGas ? parseInt(block.baseFeePerGas) : 0, }; - // rpc call to get the merkle proof what value is at key on the SignalService contract - const proof: EthGetProofResponse = await provider.send("eth_getProof", [ - opts.srcSignalServiceAddress, - [key], - block.hash, - ]); - - if (proof.storageProof[0].value !== "0x1") { - throw Error("invalid proof"); - } + return { block, blockHeader }; + } + private static getSignalProof(proof: EthGetProofResponse, blockHeader: BlockHeader) { // RLP encode the proof together for LibTrieProof to decode const encodedProof = ethers.utils.defaultAbiCoder.encode( ["bytes", "bytes"], @@ -89,49 +78,45 @@ class ProofService implements Prover { return signalProof; } - async GenerateReleaseProof(opts: GenerateReleaseProofOpts): Promise { - const key = ethers.utils.keccak256( - ethers.utils.solidityPack( - ["address", "bytes32"], - [opts.sender, opts.msgHash] - ) - ); + async GenerateProof(opts: GenerateProofOpts): Promise { + const key = ProofService.getKey(opts); - const provider = this.providerMap.get(opts.destChain); + const provider = this.providerMap.get(opts.srcChain); const contract = new Contract( - opts.srcHeaderSyncAddress, + opts.destHeaderSyncAddress, HeaderSync, - this.providerMap.get(opts.srcChain) + this.providerMap.get(opts.destChain) ); - const latestSyncedHeader = await contract.getLatestSyncedHeader(); + const { block, blockHeader } = await ProofService.getBlockAndBlockHeader(contract, provider) - const block: Block = await provider.send("eth_getBlockByHash", [ - latestSyncedHeader, - false, + // rpc call to get the merkle proof what value is at key on the SignalService contract + const proof: EthGetProofResponse = await provider.send("eth_getProof", [ + opts.srcSignalServiceAddress, + [key], + block.hash, ]); - const logsBloom = block.logsBloom.toString().substring(2); + if (proof.storageProof[0].value !== "0x1") { + throw Error("invalid proof"); + } - const blockHeader: BlockHeader = { - parentHash: block.parentHash, - ommersHash: block.sha3Uncles, - beneficiary: block.miner, - stateRoot: block.stateRoot, - transactionsRoot: block.transactionsRoot, - receiptsRoot: block.receiptsRoot, - logsBloom: logsBloom.match(/.{1,64}/g)!.map((s: string) => "0x" + s), - difficulty: block.difficulty, - height: block.number, - gasLimit: block.gasLimit, - gasUsed: block.gasUsed, - timestamp: block.timestamp, - extraData: block.extraData, - mixHash: block.mixHash, - nonce: block.nonce, - baseFeePerGas: block.baseFeePerGas ? parseInt(block.baseFeePerGas) : 0, - }; + return ProofService.getSignalProof(proof, blockHeader); + } + + async GenerateReleaseProof(opts: GenerateReleaseProofOpts): Promise { + const key = ProofService.getKey(opts); + + const provider = this.providerMap.get(opts.destChain); + + const contract = new Contract( + opts.srcHeaderSyncAddress, + HeaderSync, + this.providerMap.get(opts.srcChain) + ); + + const { block, blockHeader } = await ProofService.getBlockAndBlockHeader(contract, provider) // rpc call to get the merkle proof what value is at key on the SignalService contract const proof: EthGetProofResponse = await provider.send("eth_getProof", [ @@ -140,27 +125,11 @@ class ProofService implements Prover { block.hash, ]); - console.log(proof); - if (proof.storageProof[0].value !== "0x3") { throw Error("invalid proof"); } - // RLP encode the proof together for LibTrieProof to decode - const encodedProof = ethers.utils.defaultAbiCoder.encode( - ["bytes", "bytes"], - [RLP.encode(proof.accountProof), RLP.encode(proof.storageProof[0].proof)] - ); - - // encode the SignalProof struct from LibBridgeSignal - const signalProof = ethers.utils.defaultAbiCoder.encode( - [ - "tuple(tuple(bytes32 parentHash, bytes32 ommersHash, address beneficiary, bytes32 stateRoot, bytes32 transactionsRoot, bytes32 receiptsRoot, bytes32[8] logsBloom, uint256 difficulty, uint128 height, uint64 gasLimit, uint64 gasUsed, uint64 timestamp, bytes extraData, bytes32 mixHash, uint64 nonce, uint256 baseFeePerGas) header, bytes proof)", - ], - [{ header: blockHeader, proof: encodedProof }] - ); - - return signalProof; + return ProofService.getSignalProof(proof, blockHeader); } } From fbff9a069c3b1b5dbae86cfc7582ba91e5525cfc Mon Sep 17 00:00:00 2001 From: Shadab Khan Date: Thu, 23 Feb 2023 15:38:50 +0530 Subject: [PATCH 22/23] feat(bridge-ui): check if failed tokens are released --- .../src/components/Transaction.svelte | 26 +++++++++++++++++++ .../bridge-ui/src/constants/abi/Bridge.ts | 19 ++++++++++++++ packages/bridge-ui/src/domain/message.ts | 1 + 3 files changed, 46 insertions(+) diff --git a/packages/bridge-ui/src/components/Transaction.svelte b/packages/bridge-ui/src/components/Transaction.svelte index 0f6f8b1401..6143d1805f 100644 --- a/packages/bridge-ui/src/components/Transaction.svelte +++ b/packages/bridge-ui/src/components/Transaction.svelte @@ -27,6 +27,7 @@ import { fetchSigner, switchNetwork } from "@wagmi/core"; import Bridge from "../constants/abi/Bridge"; import ButtonWithTooltip from "./ButtonWithTooltip.svelte"; + import TokenVault from "../constants/abi/TokenVault"; export let transaction: BridgeTransaction; @@ -151,6 +152,29 @@ ); transaction.status = await contract.getMessageStatus(transaction.msgHash); + if(transaction.status === MessageStatus.Failed) { + if(transaction.message.data !== "0x") { + const srcTokenVaultContract = new ethers.Contract( + $chainIdToTokenVaultAddress.get(transaction.fromChainId), + TokenVault, + $providers.get(chains[transaction.message.srcChainId.toNumber()].id) + ) + const {token, amount} = await srcTokenVaultContract.messageDeposits(transaction.msgHash); + if(token === ethers.constants.AddressZero && amount.eq(0)) { + transaction.status = MessageStatus.FailedReleased; + } + } else { + const srcBridgeContract = new ethers.Contract( + chains[transaction.fromChainId].bridgeAddress, + Bridge, + $providers.get(chains[transaction.message.srcChainId.toNumber()].id) + ) + const {token, amount} = await srcBridgeContract.isEtherReleased(transaction.msgHash); + if(token === ethers.constants.AddressZero && amount.eq(0)) { + transaction.status = MessageStatus.FailedReleased; + } + } + } transaction = transaction; if (transaction.status === MessageStatus.Done) clearInterval(interval); }, 20 * 1000); @@ -215,6 +239,8 @@ {:else if transaction.status === MessageStatus.Done} Claimed + {:else if transaction.status === MessageStatus.FailedReleased} + Released {/if} diff --git a/packages/bridge-ui/src/constants/abi/Bridge.ts b/packages/bridge-ui/src/constants/abi/Bridge.ts index 2e43ccef75..9c2612f484 100644 --- a/packages/bridge-ui/src/constants/abi/Bridge.ts +++ b/packages/bridge-ui/src/constants/abi/Bridge.ts @@ -414,6 +414,25 @@ export default [ "stateMutability": "view", "type": "function" }, + { + "inputs": [ + { + "internalType": "bytes32", + "name": "msgHash", + "type": "bytes32" + } + ], + "name": "isEtherReleased", + "outputs": [ + { + "internalType": "bool", + "name": "", + "type": "bool" + } + ], + "stateMutability": "view", + "type": "function" + }, { "inputs": [ { diff --git a/packages/bridge-ui/src/domain/message.ts b/packages/bridge-ui/src/domain/message.ts index 0abaa785c7..1abf1c29bc 100644 --- a/packages/bridge-ui/src/domain/message.ts +++ b/packages/bridge-ui/src/domain/message.ts @@ -5,6 +5,7 @@ enum MessageStatus { Retriable, Done, Failed, + FailedReleased } type Message = { From 0b61b61cb54d9b98917bfe48ec287b880d2ec78a Mon Sep 17 00:00:00 2001 From: Shadab Khan Date: Fri, 24 Feb 2023 07:35:39 +0530 Subject: [PATCH 23/23] fix(bridge-ui): fix isEtherReleased check --- packages/bridge-ui/src/components/Transaction.svelte | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/bridge-ui/src/components/Transaction.svelte b/packages/bridge-ui/src/components/Transaction.svelte index 6143d1805f..79d810dc21 100644 --- a/packages/bridge-ui/src/components/Transaction.svelte +++ b/packages/bridge-ui/src/components/Transaction.svelte @@ -169,8 +169,8 @@ Bridge, $providers.get(chains[transaction.message.srcChainId.toNumber()].id) ) - const {token, amount} = await srcBridgeContract.isEtherReleased(transaction.msgHash); - if(token === ethers.constants.AddressZero && amount.eq(0)) { + const isFailedMessageResolved = await srcBridgeContract.isEtherReleased(transaction.msgHash); + if(isFailedMessageResolved) { transaction.status = MessageStatus.FailedReleased; } }