Skip to content

Commit

Permalink
feat: add weth gateway registration (#44)
Browse files Browse the repository at this point in the history
  • Loading branch information
TucksonDev authored Feb 29, 2024
1 parent 2186ccc commit 3f7b15a
Show file tree
Hide file tree
Showing 7 changed files with 482 additions and 5 deletions.
66 changes: 66 additions & 0 deletions examples/create-token-bridge-eth/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,8 @@ import { Chain, createPublicClient, http, defineChain } from 'viem';
import { privateKeyToAccount } from 'viem/accounts';
import { arbitrumSepolia } from 'viem/chains';
import {
createTokenBridgePrepareSetWethGatewayTransactionReceipt,
createTokenBridgePrepareSetWethGatewayTransactionRequest,
createTokenBridgePrepareTransactionReceipt,
createTokenBridgePrepareTransactionRequest,
} from '@arbitrum/orbit-sdk';
Expand Down Expand Up @@ -90,12 +92,76 @@ async function main() {
console.log(
`Transaction hash for second retryable is ${orbitChainRetryableReceipts[1].transactionHash}`,
);
if (orbitChainRetryableReceipts[0].status !== 'success') {
throw new Error(
`First retryable status is not success: ${orbitChainRetryableReceipts[0].status}. Aborting...`,
);
}
if (orbitChainRetryableReceipts[1].status !== 'success') {
throw new Error(
`Second retryable status is not success: ${orbitChainRetryableReceipts[1].status}. Aborting...`,
);
}

// fetching the TokenBridge contracts
const tokenBridgeContracts = await txReceipt.getTokenBridgeContracts({
parentChainPublicClient,
});
console.log(`TokenBridge contracts:`, tokenBridgeContracts);

// verifying L2 contract existence
const orbitChainRouterBytecode = await orbitChainPublicClient.getBytecode({
address: tokenBridgeContracts.orbitChainContracts.router,
});

if (!orbitChainRouterBytecode || orbitChainRouterBytecode == '0x') {
throw new Error(
`TokenBridge deployment seems to have failed since orbit chain contracts do not have code`,
);
}

// set weth gateway
const setWethGatewayTxRequest = await createTokenBridgePrepareSetWethGatewayTransactionRequest({
rollup: process.env.ROLLUP_ADDRESS as `0x${string}`,
parentChainPublicClient,
orbitChainPublicClient,
account: rollupOwner.address,
retryableGasOverrides: {
gasLimit: {
percentIncrease: 200n,
},
},
});

// sign and send the transaction
const setWethGatewayTxHash = await parentChainPublicClient.sendRawTransaction({
serializedTransaction: await rollupOwner.signTransaction(setWethGatewayTxRequest),
});

// get the transaction receipt after waiting for the transaction to complete
const setWethGatewayTxReceipt = createTokenBridgePrepareSetWethGatewayTransactionReceipt(
await parentChainPublicClient.waitForTransactionReceipt({ hash: setWethGatewayTxHash }),
);

console.log(
`Weth gateway set in ${getBlockExplorerUrl(parentChain)}/tx/${
setWethGatewayTxReceipt.transactionHash
}`,
);

// Wait for retryables to execute
const orbitChainSetWethGatewayRetryableReceipt = await setWethGatewayTxReceipt.waitForRetryables({
orbitPublicClient: orbitChainPublicClient,
});
console.log(`Retryables executed`);
console.log(
`Transaction hash for retryable is ${orbitChainSetWethGatewayRetryableReceipt[0].transactionHash}`,
);
if (orbitChainSetWethGatewayRetryableReceipt[0].status !== 'success') {
throw new Error(
`Retryable status is not success: ${orbitChainSetWethGatewayRetryableReceipt[0].status}. Aborting...`,
);
}
}

main();
35 changes: 35 additions & 0 deletions src/createRollupFetchCoreContracts.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,35 @@
import { Address, PublicClient } from 'viem';
import { validParentChainId } from './types/ParentChain';
import { CoreContracts } from './types/CoreContracts';
import { createRollupFetchTransactionHash } from './createRollupFetchTransactionHash';
import { createRollupPrepareTransactionReceipt } from './createRollupPrepareTransactionReceipt';

export type CreateRollupFetchCoreContractsParams = {
rollup: Address;
publicClient: PublicClient;
};

export async function createRollupFetchCoreContracts({
rollup,
publicClient,
}: CreateRollupFetchCoreContractsParams): Promise<CoreContracts> {
const chainId = publicClient.chain?.id;

if (!validParentChainId(chainId)) {
throw new Error('chainId is undefined');
}

// getting core contract addresses
const transactionHash = await createRollupFetchTransactionHash({
rollup,
publicClient,
});

const transactionReceipt = createRollupPrepareTransactionReceipt(
await publicClient.waitForTransactionReceipt({
hash: transactionHash,
}),
);

return transactionReceipt.getCoreContracts();
}
32 changes: 32 additions & 0 deletions src/createTokenBridge-ethers.ts
Original file line number Diff line number Diff line change
Expand Up @@ -192,6 +192,38 @@ const getEstimateForDeployingFactory = async (
return deployFactoryGasParams;
};

export const getEstimateForSettingGateway = async (
l1ChainOwnerAddress: Address,
l1UpgradeExecutorAddress: Address,
l1GatewayRouterAddress: Address,
setGatewaysCalldata: `0x${string}`,
l1Provider: ethers.providers.Provider,
l2Provider: ethers.providers.Provider,
) => {
//// run retryable estimate for setting a token gateway in the router
const l1ToL2MsgGasEstimate = new L1ToL2MessageGasEstimator(l2Provider);

const setGatewaysGasParams = await l1ToL2MsgGasEstimate.estimateAll(
{
from: l1UpgradeExecutorAddress,
to: l1GatewayRouterAddress,
l2CallValue: BigNumber.from(0),
excessFeeRefundAddress: l1ChainOwnerAddress,
callValueRefundAddress: l1ChainOwnerAddress,
data: setGatewaysCalldata,
},
await getBaseFee(l1Provider),
l1Provider,
);

return {
gasLimit: setGatewaysGasParams.gasLimit.toBigInt(),
maxFeePerGas: setGatewaysGasParams.maxFeePerGas.toBigInt(),
maxSubmissionCost: setGatewaysGasParams.maxSubmissionCost.toBigInt(),
deposit: setGatewaysGasParams.deposit.toBigInt(),
};
};

const registerNewNetwork = async (
l1Provider: JsonRpcProvider,
l2Provider: JsonRpcProvider,
Expand Down
63 changes: 58 additions & 5 deletions src/createTokenBridge.integration.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,9 @@ import {
createPublicClient,
encodeFunctionData,
http,
maxInt256,
parseEther,
zeroAddress,
parseAbi,
} from 'viem';
import { execSync } from 'node:child_process';

Expand All @@ -14,12 +14,11 @@ import { getNitroTestnodePrivateKeyAccounts } from './testHelpers';
import { createTokenBridgePrepareTransactionRequest } from './createTokenBridgePrepareTransactionRequest';
import { createTokenBridgePrepareTransactionReceipt } from './createTokenBridgePrepareTransactionReceipt';
import { deployTokenBridgeCreator } from './createTokenBridge-testHelpers';
import {
CreateTokenBridgeEnoughCustomFeeTokenAllowanceParams,
createTokenBridgeEnoughCustomFeeTokenAllowance,
} from './createTokenBridgeEnoughCustomFeeTokenAllowance';
import { CreateTokenBridgeEnoughCustomFeeTokenAllowanceParams } from './createTokenBridgeEnoughCustomFeeTokenAllowance';
import { createTokenBridgePrepareCustomFeeTokenApprovalTransactionRequest } from './createTokenBridgePrepareCustomFeeTokenApprovalTransactionRequest';
import { erc20 } from './contracts';
import { createTokenBridgePrepareSetWethGatewayTransactionRequest } from './createTokenBridgePrepareSetWethGatewayTransactionRequest';
import { createTokenBridgePrepareSetWethGatewayTransactionReceipt } from './createTokenBridgePrepareSetWethGatewayTransactionReceipt';

type TestnodeInformation = {
rollup: `0x${string}`;
Expand Down Expand Up @@ -161,6 +160,60 @@ it(`successfully deploys token bridge contracts through token bridge creator`, a
expect(tokenBridgeContracts.orbitChainContracts.beaconProxyFactory).not.toEqual(zeroAddress);
expect(tokenBridgeContracts.orbitChainContracts.upgradeExecutor).not.toEqual(zeroAddress);
expect(tokenBridgeContracts.orbitChainContracts.multicall).not.toEqual(zeroAddress);

// set weth gateway
const setWethGatewayTxRequest = await createTokenBridgePrepareSetWethGatewayTransactionRequest({
rollup: testnodeInformation.rollup,
parentChainPublicClient: nitroTestnodeL1Client,
orbitChainPublicClient: nitroTestnodeL2Client,
account: l2RollupOwner.address,
retryableGasOverrides: {
gasLimit: {
base: 100_000n,
},
},
tokenBridgeCreatorAddressOverride: tokenBridgeCreator,
});

// sign and send the transaction
const setWethGatewayTxHash = await nitroTestnodeL1Client.sendRawTransaction({
serializedTransaction: await l2RollupOwner.signTransaction(setWethGatewayTxRequest),
});

// get the transaction receipt after waiting for the transaction to complete
const setWethGatewayTxReceipt = createTokenBridgePrepareSetWethGatewayTransactionReceipt(
await nitroTestnodeL1Client.waitForTransactionReceipt({ hash: setWethGatewayTxHash }),
);

// checking retryables execution
const orbitChainSetGatewayRetryableReceipt = await setWethGatewayTxReceipt.waitForRetryables({
orbitPublicClient: nitroTestnodeL2Client,
});
expect(orbitChainSetGatewayRetryableReceipt).toHaveLength(1);
expect(orbitChainSetGatewayRetryableReceipt[0].status).toEqual('success');

// verify weth gateway (parent chain)
const registeredWethGatewayOnParentChain = await nitroTestnodeL1Client.readContract({
address: tokenBridgeContracts.parentChainContracts.router,
abi: parseAbi(['function l1TokenToGateway(address) view returns (address)']),
functionName: 'l1TokenToGateway',
args: [tokenBridgeContracts.parentChainContracts.weth],
});
expect(registeredWethGatewayOnParentChain).toEqual(
tokenBridgeContracts.parentChainContracts.wethGateway,
);

// verify weth gateway (orbit chain)
// Note: we pass the address of the token on the parent chain when asking for the registered gateway on the orbit chain
const registeredWethGatewayOnOrbitChain = await nitroTestnodeL2Client.readContract({
address: tokenBridgeContracts.orbitChainContracts.router,
abi: parseAbi(['function l1TokenToGateway(address) view returns (address)']),
functionName: 'l1TokenToGateway',
args: [tokenBridgeContracts.parentChainContracts.weth],
});
expect(registeredWethGatewayOnOrbitChain).toEqual(
tokenBridgeContracts.orbitChainContracts.wethGateway,
);
});

it(`successfully deploys token bridge contracts with a custom fee token through token bridge creator`, async () => {
Expand Down
56 changes: 56 additions & 0 deletions src/createTokenBridgePrepareSetWethGatewayTransactionReceipt.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,56 @@
import { PublicClient, TransactionReceipt } from 'viem';
import { L1ToL2MessageStatus, L1TransactionReceipt } from '@arbitrum/sdk';
import { TransactionReceipt as EthersTransactionReceipt } from '@ethersproject/abstract-provider';

import { publicClientToProvider } from './ethers-compat/publicClientToProvider';
import { viemTransactionReceiptToEthersTransactionReceipt } from './ethers-compat/viemTransactionReceiptToEthersTransactionReceipt';
import { ethersTransactionReceiptToViemTransactionReceipt } from './ethers-compat/ethersTransactionReceiptToViemTransactionReceipt';

type RedeemedRetryableTicket = {
status: L1ToL2MessageStatus.REDEEMED;
l2TxReceipt: EthersTransactionReceipt;
};

export type WaitForRetryablesParameters = {
orbitPublicClient: PublicClient;
};

export type WaitForRetryablesResult = [TransactionReceipt];

export type CreateTokenBridgeSetWethGatewayTransactionReceipt = TransactionReceipt & {
waitForRetryables(params: WaitForRetryablesParameters): Promise<WaitForRetryablesResult>;
};

export function createTokenBridgePrepareSetWethGatewayTransactionReceipt(
txReceipt: TransactionReceipt,
): CreateTokenBridgeSetWethGatewayTransactionReceipt {
return {
...txReceipt,
waitForRetryables: async function ({
orbitPublicClient,
}: WaitForRetryablesParameters): Promise<WaitForRetryablesResult> {
const ethersTxReceipt = viemTransactionReceiptToEthersTransactionReceipt(txReceipt);
const parentChainTxReceipt = new L1TransactionReceipt(ethersTxReceipt);
const orbitProvider = publicClientToProvider(orbitPublicClient);
const messages = await parentChainTxReceipt.getL1ToL2Messages(orbitProvider);
const messagesResults = await Promise.all(messages.map((message) => message.waitForStatus()));

if (messagesResults.length !== 1) {
throw Error(`Unexpected number of retryable tickets: ${messagesResults.length}`);
}

if (messagesResults[0].status !== L1ToL2MessageStatus.REDEEMED) {
throw Error(`Unexpected status for retryable ticket: ${messages[0].retryableCreationId}`);
}

return (
// these type casts are both fine as we already checked everything above
(messagesResults as unknown as [RedeemedRetryableTicket])
//
.map((result) =>
ethersTransactionReceiptToViemTransactionReceipt(result.l2TxReceipt),
) as WaitForRetryablesResult
);
},
};
}
Loading

0 comments on commit 3f7b15a

Please sign in to comment.