Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: multiple withdrawal in 1 tx #72

Merged
merged 4 commits into from
Oct 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@
"start": "rm -r .next && next build && next start",
"lint": "eslint 'src/**/*.{js,ts,tsx}'",
"prettier:format": "yarn prettier './**/*.{js,json,md,ts,tsx,yml}' --write && yarn run lint --fix",
"e2e:open": "env-cmd -f .e2e.env yarn synpress open --configFile synpress.config.ts",
"e2e:run": "env-cmd -f .e2e.env yarn synpress run --configFile synpress.config.ts --browser chrome"
"e2e:open": "NODE_ENV=test env-cmd -f .e2e.env yarn synpress open --configFile synpress.config.ts",
"e2e:run": "NODE_ENV=test env-cmd -f .e2e.env yarn synpress run --configFile synpress.config.ts --browser chrome"
},
"browserslist": [
">0.2%",
Expand Down
4 changes: 4 additions & 0 deletions src/app/global.css
Original file line number Diff line number Diff line change
Expand Up @@ -270,3 +270,7 @@ ul {
padding-left: 0;
margin-top: 0;
}

.redeem-button-container {
text-align: center;
}
52 changes: 33 additions & 19 deletions src/app/tx/[tx]/L2ToL1MsgsDisplay.tsx
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
'use client';
import { useState } from 'react';
import { ChainId, supportedL2Networks } from '@/utils/network';
import { L2ToL1MessageData } from '@/types';
import {
Expand Down Expand Up @@ -38,6 +39,7 @@ export type L2ToL1MessageDataLike = Pick<
deadlineBlock: string;
etaSeconds: number;
} | null;
l2ToL1EventIndex: number;
};
type Props = {
l2ToL1Messages: L2ToL1MessageDataLike[];
Expand All @@ -46,6 +48,7 @@ type Props = {
function L2ToL1MsgsDisplay({ l2ToL1Messages }: Props) {
const { chain } = useNetwork();
const { data: signer = null } = useSigner({ chainId: chain?.id });
const [redeeming, setIsRedeeming] = useState(false);

const renderMessage = (l2ToL1Message: L2ToL1MessageDataLike) => {
switch (l2ToL1Message.status) {
Expand Down Expand Up @@ -77,29 +80,40 @@ function L2ToL1MsgsDisplay({ l2ToL1Messages }: Props) {
{`To redeem, connect to chain ${l2ToL1Message.l1Network.chainID} (${l2ToL1Message.l1Network.name})`}
</div>
) : (
<div>
<div className="redeem-button-container">
<button
disabled={redeeming}
onClick={async () => {
if (!signer) return;
const [l2ToL1TxEvent] = await L2ToL1Message.getL2ToL1Events(
l2Provider,
{
fromBlock: l2ToL1Message.createdAtL2BlockNumber,
toBlock: l2ToL1Message.createdAtL2BlockNumber + 1,
},
);
const l2ToL1MessageWriter = new L2ToL1MessageWriter(
signer,
l2ToL1TxEvent,
);
const res = await l2ToL1MessageWriter.execute(l2Provider);
const rec = await res.wait();
if (rec.status === 1) {
alert(
`L2toL1 message successfully redeemed! ${rec.transactionHash}`,
try {
setIsRedeeming(true);
// This would create a lot of duplicated getLogs call, would be nicer if we can pass the event in but it's not serializable
// This also assume the order returned by getL2ToL1Events is always in order
const l2ToL1TxEvents =
await L2ToL1Message.getL2ToL1Events(l2Provider, {
fromBlock: l2ToL1Message.createdAtL2BlockNumber,
toBlock: l2ToL1Message.createdAtL2BlockNumber + 1,
});
const l2ToL1MessageWriter = new L2ToL1MessageWriter(
signer,
l2ToL1TxEvents[l2ToL1Message.l2ToL1EventIndex],
);
} else {
throw new Error('Failed to redeem');
const res = await l2ToL1MessageWriter.execute(l2Provider);
const rec = await res.wait();
if (rec.status === 1) {
alert(
`L2toL1 message successfully redeemed! ${rec.transactionHash}`,
);
} else {
throw new Error('Failed to redeem');
}
} catch (e: any) {
// Ignore user rejected action
if (e?.code !== 4001 && e?.code !== 'ACTION_REJECTED') {
throw e;
}
} finally {
setIsRedeeming(false);
}
}}
>
Expand Down
4 changes: 3 additions & 1 deletion src/app/tx/[tx]/page.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,7 @@ const Transaction = async ({ params }: Props) => {
l1Network,
l2Network,
createdAtL2BlockNumber,
l2ToL1EventIndex,
}) => ({
status,
confirmationInfo: confirmationInfo
Expand All @@ -151,6 +152,7 @@ const Transaction = async ({ params }: Props) => {
name: l2Network.name,
},
createdAtL2BlockNumber,
l2ToL1EventIndex,
}),
);

Expand Down Expand Up @@ -181,7 +183,7 @@ const Transaction = async ({ params }: Props) => {
<MessageDisplays
messages={allMessages}
hasL2ToL1MessagesConfirmed={l2ToL1MessagesToShow.some(
(msg) => msg.status === L2ToL1MessageStatus.CONFIRMED,
(msg) => msg.status !== L2ToL1MessageStatus.UNCONFIRMED, // Also show executed
)}
/>
</Suspense>
Expand Down
1 change: 1 addition & 0 deletions src/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -121,6 +121,7 @@ export interface L2ToL1MessageData {
l2Network: L2Network;
l2Provider: JsonRpcProvider;
createdAtL2BlockNumber: number;
l2ToL1EventIndex: number;
}

export interface L2ToL1MessageSearchResult {
Expand Down
22 changes: 13 additions & 9 deletions src/utils/getL1TxnReceipt.ts
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,20 @@ export const getL1TxnReceipt = async (
safeAddDefaultLocalNetwork();
const promises = Object.entries(supportedL1Networks).map(
async ([chainID, rpcURL]) => {
const l1Network = await getL1Network(+chainID);
const l1Provider = new StaticJsonRpcProvider(rpcURL);
try {
const l1Network = await getL1Network(+chainID);
const l1Provider = new StaticJsonRpcProvider(rpcURL);

const rec = await l1Provider.getTransactionReceipt(txnHash);
if (rec) {
return {
l1TxnReceipt: new L1TransactionReceipt(rec),
l1Network,
l1Provider,
};
const rec = await l1Provider.getTransactionReceipt(txnHash);
if (rec) {
return {
l1TxnReceipt: new L1TransactionReceipt(rec),
l1Network,
l1Provider,
};
}
} catch (e) {
console.warn(rpcURL, 'not working');
}
},
);
Expand Down
19 changes: 16 additions & 3 deletions src/utils/getL2ToL1Messages.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@ import {
import {
getL1Network,
getL2Network,
L2ToL1MessageReader,
L2ToL1MessageStatus,
L2TransactionReceipt,
} from '@arbitrum/sdk';
Expand All @@ -25,6 +26,12 @@ export const getL2ToL1Messages = async (
// TODO
const l1ChainID = l2Network.partnerChainID as 1 | 5;
const l1Provider = new JsonRpcProvider(supportedL1Networks[l1ChainID]);
try {
await l1Provider.getBlockNumber();
} catch (e) {
console.warn(supportedL1Networks[l1ChainID], 'not working');
return null;
}
const [l1Network, l1BlogNumber, receipt] = await Promise.all([
getL1Network(l1ChainID),
l1Provider.getBlockNumber(),
Expand All @@ -44,9 +51,13 @@ export const getL2ToL1Messages = async (
}

const l2Receipt = new L2TransactionReceipt(receipt);
const l2ToL1Messages = await l2Receipt.getL2ToL1Messages(l1Provider);
const l2MessagesData: Promise<L2ToL1MessageData>[] = l2ToL1Messages.map(
async (l2ToL1Message) => {
const l2ToL1Events = l2Receipt.getL2ToL1Events();
const l2MessagesData: Promise<L2ToL1MessageData>[] = l2ToL1Events.map(
async (l2ToL1Event, l2ToL1EventIndex) => {
const l2ToL1Message = new L2ToL1MessageReader(
l1Provider,
l2ToL1Event,
);
try {
const status = await l2ToL1Message.status(l2Provider);
const deadlineBlock =
Expand All @@ -70,6 +81,7 @@ export const getL2ToL1Messages = async (
l2Network,
l2Provider,
createdAtL2BlockNumber: l2Receipt.blockNumber,
l2ToL1EventIndex,
};
} catch (e) {
const expectedError = "batch doesn't exist";
Expand All @@ -87,6 +99,7 @@ export const getL2ToL1Messages = async (
l2Network,
l2Provider,
createdAtL2BlockNumber: l2Receipt.blockNumber,
l2ToL1EventIndex,
};
} else {
throw e;
Expand Down
24 changes: 14 additions & 10 deletions src/utils/network.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ const MAINNET_INFURA_RPC_URL = `https://mainnet.infura.io/v3/${INFURA_KEY}`;
const GOERLI_INFURA_RPC_URL = `https://goerli.infura.io/v3/${INFURA_KEY}`;
const ARBITRUM_INFURA_RPC_URL = `https://arbitrum-mainnet.infura.io/v3/${INFURA_KEY}`;
const ARBITRUM_GOERLI_INFURA_RPC_URL = `https://arbitrum-goerli.infura.io/v3/${INFURA_KEY}`;
const LOCAL_GETH_RPC_URL = `http://localhost:8545`;

export enum ChainId {
// L1
Expand All @@ -25,6 +26,8 @@ export enum ChainId {
ArbitrumLocal = 412346,
}

const isE2e = process.env.NODE_ENV === 'test';

type RpcMap = Record<ChainId, string>;
export const rpcURLs: RpcMap = {
// L1
Expand All @@ -37,10 +40,6 @@ export const rpcURLs: RpcMap = {
env: process.env.NEXT_PUBLIC_GOERLI_RPC_URL,
fallback: GOERLI_INFURA_RPC_URL,
}),
[ChainId.Local]: loadEnvironmentVariableWithFallback({
env: process.env.NEXT_PUBLIC_LOCAL_ETHEREUM_RPC_URL,
fallback: MAINNET_INFURA_RPC_URL,
}),
// L2
[ChainId.ArbitrumOne]: loadEnvironmentVariableWithFallback({
env: process.env.NEXT_PUBLIC_ARBITRUM_RPC_URL,
Expand All @@ -51,27 +50,32 @@ export const rpcURLs: RpcMap = {
env: process.env.NEXT_PUBLIC_ARBITRUM_GOERLI_RPC_URL,
fallback: ARBITRUM_GOERLI_INFURA_RPC_URL,
}),
[ChainId.ArbitrumLocal]: loadEnvironmentVariableWithFallback({
env: process.env.NEXT_PUBLIC_LOCAL_ARBITRUM_RPC_URL,
fallback: ARBITRUM_INFURA_RPC_URL,
}),
[ChainId.ArbitrumNova]: loadEnvironmentVariableWithFallback({
env: process.env.NEXT_PUBLIC_ARBITRUM_NOVA_RPC_URL,
fallback: 'https://nova.arbitrum.io/rpc',
}),
// E2E RPCs
[ChainId.Local]: loadEnvironmentVariableWithFallback({
env: process.env.NEXT_PUBLIC_LOCAL_ETHEREUM_RPC_URL,
fallback: LOCAL_GETH_RPC_URL,
}),
[ChainId.ArbitrumLocal]: loadEnvironmentVariableWithFallback({
env: process.env.NEXT_PUBLIC_LOCAL_ARBITRUM_RPC_URL,
fallback: ARBITRUM_INFURA_RPC_URL,
}),
};

export const supportedL1Networks: Partial<RpcMap> = {
[ChainId.Mainnet]: rpcURLs[ChainId.Mainnet],
[ChainId.Goerli]: rpcURLs[ChainId.Goerli],
[ChainId.Local]: rpcURLs[ChainId.Local],
...(isE2e ? { [ChainId.Local]: rpcURLs[ChainId.Local] } : {}),
};

export const supportedL2Networks: Partial<RpcMap> = {
[ChainId.ArbitrumOne]: rpcURLs[ChainId.ArbitrumOne],
[ChainId.ArbitrumNova]: rpcURLs[ChainId.ArbitrumNova],
[ChainId.ArbitrumGoerli]: rpcURLs[ChainId.ArbitrumGoerli],
[ChainId.ArbitrumLocal]: rpcURLs[ChainId.ArbitrumLocal],
...(isE2e ? { [ChainId.ArbitrumLocal]: rpcURLs[ChainId.ArbitrumLocal] } : {}),
};

// Because of React Server Component, we might need to call this function more than once
Expand Down
4 changes: 2 additions & 2 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@
resolved "https://registry.yarnpkg.com/@adraffy/ens-normalize/-/ens-normalize-1.9.0.tgz#223572538f6bea336750039bb43a4016dcc8182d"
integrity sha512-iowxq3U30sghZotgl4s/oJRci6WPBfNO5YYgk2cIOMCHr3LeGPcsZjCEr+33Q4N+oV3OABDAtA+pyvWjbvBifQ==

"@adraffy/ens-normalize@git+https://github.com/ricmoo/ens-normalize.js.git":
"@adraffy/ens-normalize@https://github.com/ricmoo/ens-normalize.js":
version "1.9.0"
resolved "git+https://github.com/ricmoo/ens-normalize.js.git#2d040533e57e4f25f9a7cc4715e219658ad454b5"
resolved "https://github.com/ricmoo/ens-normalize.js#2d040533e57e4f25f9a7cc4715e219658ad454b5"

"@ampproject/remapping@^2.2.0":
version "2.2.1"
Expand Down