diff --git a/packages/l1-l3-teleport/README.md b/packages/l1-l3-teleport/README.md index 5db620e..caa57bd 100644 --- a/packages/l1-l3-teleport/README.md +++ b/packages/l1-l3-teleport/README.md @@ -14,6 +14,10 @@ See [`monitor-deposit-status.ts`](./scripts/monitor-deposit-status.ts) for monit See [`monitor-eth-deposit-status.ts`](./scripts//monitor-eth-deposit-status.ts) for monitoring ETH deposits. +See [`recover-deposit.ts](./scripts/recover-deposit.ts) for recovering a failed ERC20 deposit. (Assumes `DEVNET_PRIVATE_KEY` is funded on L2 and L3) + +See [`recover-eth-deposit.ts`](./scripts/recover-eth-deposit.ts) for recovering a failed ETH deposit. (Assumes `DEVNET_PRIVATE_KEY` is funded on L2 and L3) + ### Paying for retryables When initiating an ERC20 deposit, there are multiple ways retryable fees can be paid. @@ -35,6 +39,10 @@ yarn initiate-eth-deposit --help yarn monitor-deposit-status yarn monitor-eth-deposit-status + +yarn recover-deposit + +yarn recover-eth-deposit ``` ## Configure environment variables diff --git a/packages/l1-l3-teleport/package.json b/packages/l1-l3-teleport/package.json index fc78d2f..e98baf7 100644 --- a/packages/l1-l3-teleport/package.json +++ b/packages/l1-l3-teleport/package.json @@ -10,7 +10,7 @@ "monitor-eth-deposit-status": "ts-node scripts/monitor-eth-deposit-status.ts" }, "dependencies": { - "godzillaba-arbitrum-sdk": "^3.3.3-teleport", + "@arbitrum/sdk": "3.5.1-teleporter.0", "ts-node": "^10.9.2", "typescript": "^5.3.3", "yargs": "^17.7.2" diff --git a/packages/l1-l3-teleport/scripts/initiate-deposit.ts b/packages/l1-l3-teleport/scripts/initiate-deposit.ts index 59eb3f7..87883ed 100644 --- a/packages/l1-l3-teleport/scripts/initiate-deposit.ts +++ b/packages/l1-l3-teleport/scripts/initiate-deposit.ts @@ -1,7 +1,7 @@ -import { providers, Wallet, BigNumber, ethers } from "ethers" +import { providers, Wallet, BigNumber } from "ethers" import { arbLog, requireEnvVariables } from 'arb-shared-dependencies' -import { Erc20L1L3Bridger, getL2Network } from "godzillaba-arbitrum-sdk" -import { ERC20__factory } from "godzillaba-arbitrum-sdk/dist/lib/abi/factories/ERC20__factory" +import { Erc20L1L3Bridger, getL2Network } from "@arbitrum/sdk" +import { ERC20__factory } from "@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory" import yargs from 'yargs/yargs' import { hideBin } from "yargs/helpers" @@ -17,7 +17,7 @@ const l3Provider = new providers.JsonRpcProvider(process.env.L3RPC) const l1Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l1Provider) const main = async (params: { - l1Token: string, amount: BigNumber, l3Recipient?: string, skipFeeToken?: boolean + l1Token: string, amount: BigNumber, l3Recipient?: string, skipGasToken?: boolean }) => { await arbLog(`Bridging tokens from L1 to L3`) @@ -44,12 +44,12 @@ const main = async (params: { const l2ChainId = (await l2Provider.getNetwork()).chainId const l3ChainId = (await l3Provider.getNetwork()).chainId const signerAddress = await l1Signer.getAddress() - const l3FeeTokenL1Address = await bridger.l1FeeTokenAddress(l1Provider, l2Provider) + const l3GasTokenL1Addr = await bridger.getGasTokenOnL1(l1Provider, l2Provider) const feeTokenSymbol = bridger.l2FeeTokenAddress ? await ERC20__factory.connect(bridger.l2FeeTokenAddress, l2Provider).symbol() : 'ETH' console.log('L1 Token:', params.l1Token) - console.log('L3 Fee Token:', feeTokenSymbol || 'ETH') - console.log('L3 Fee Token Address on L2:', bridger.l2FeeTokenAddress || 'ETH') - console.log('L3 Fee Token Address on L1:', l3FeeTokenL1Address) + console.log('L3 Gas Token:', feeTokenSymbol || 'ETH') + console.log('L3 Gas Token Address on L2:', bridger.l2FeeTokenAddress || 'ETH') + console.log('L3 Gas Token Address on L1:', l3GasTokenL1Addr) console.log('Recipient:', params.l3Recipient || signerAddress) console.log('Amount:', params.amount.toString()) console.log('L1 chain id:', l1ChainId) @@ -66,13 +66,13 @@ const main = async (params: { amount: params.amount, l2Provider, l3Provider, - to: params.l3Recipient, // optional, defaults to signer's address + destinationAddress: params.l3Recipient, // optional, defaults to signer's address /** * Optional, defaults to false. * Skip paying the L2->L3 fee if the L3 doesn't use ETH for fees * This has no effect if the L3 uses ETH for fees. */ - skipFeeToken: params.skipFeeToken + skipGasToken: params.skipGasToken }) console.log('Done') @@ -132,7 +132,7 @@ const args = yargs(hideBin(process.argv)) type: 'string', description: 'L3 recipient address' }, - skipFeeToken: { + skipGasToken: { type: 'boolean', description: 'Skip paying the L2->L3 fee if the L3 doesn\'t use ETH for fees.\nThis has no effect if the L3 uses ETH for fees.', default: false @@ -145,7 +145,7 @@ main({ l1Token: args.l1Token, amount: BigNumber.from(args.amount), l3Recipient: args.l3Recipient, - skipFeeToken: args.skipFeeToken + skipGasToken: args.skipGasToken }).then(() => process.exit(0)).catch(error => { console.error(error) process.exit(1) diff --git a/packages/l1-l3-teleport/scripts/initiate-eth-deposit.ts b/packages/l1-l3-teleport/scripts/initiate-eth-deposit.ts index c8d2115..0cef8d3 100644 --- a/packages/l1-l3-teleport/scripts/initiate-eth-deposit.ts +++ b/packages/l1-l3-teleport/scripts/initiate-eth-deposit.ts @@ -1,7 +1,6 @@ import { providers, Wallet, BigNumber } from "ethers" import { arbLog, requireEnvVariables } from 'arb-shared-dependencies' -import { Erc20L1L3Bridger, EthL1L3Bridger, getL2Network } from "godzillaba-arbitrum-sdk" -import { ERC20__factory } from "godzillaba-arbitrum-sdk/dist/lib/abi/factories/ERC20__factory" +import { EthL1L3Bridger, getL2Network } from "@arbitrum/sdk" import yargs from 'yargs/yargs' import { hideBin } from "yargs/helpers" @@ -49,7 +48,7 @@ const main = async (params: { amount: params.amount, l2Provider, l3Provider, - to: params.l3Recipient, // optional, defaults to signer's address + destinationAddress: params.l3Recipient, // optional, defaults to signer's address }) console.log('Done') /** diff --git a/packages/l1-l3-teleport/scripts/monitor-deposit-status.ts b/packages/l1-l3-teleport/scripts/monitor-deposit-status.ts index 75e51e0..e1ee833 100644 --- a/packages/l1-l3-teleport/scripts/monitor-deposit-status.ts +++ b/packages/l1-l3-teleport/scripts/monitor-deposit-status.ts @@ -1,6 +1,6 @@ import { providers } from "ethers" import { arbLog, requireEnvVariables } from 'arb-shared-dependencies' -import { Erc20L1L3Bridger, L1ToL2MessageStatus, getL2Network } from "godzillaba-arbitrum-sdk" +import { Erc20L1L3Bridger, L1ToL2MessageStatus, getL2Network } from "@arbitrum/sdk" // Importing configuration // require('dotenv').config() @@ -33,20 +33,13 @@ const main = async (txHash: string) => { * Get deposit status */ console.log('Getting deposit status...') - const depositStatus = await bridger.getDepositMessages({ txHash, l1Provider, l2Provider, l3Provider }) - - - - /** - * If any of these retryables fail (i.e. FUNDS_DEPOSITED_ON_L2), manually redeem them in the order displayed below - * Note that anyone can manually redeem these retryables, not just the sender of the deposit - * If the user opts to skip payment for the L2->L3 retryable, depositStatus.l2l3TokenBridge MUST be redeemed manually - */ - console.log(`L1 to L2 Fee Token Bridge: ${statusToText[await depositStatus.l1l2FeeTokenBridge?.status() || 'NA']}`) - console.log(`L1 to L2 Token Bridge: ${statusToText[await depositStatus.l1l2TokenBridge.status()]}`) - console.log(`L2 Forwarder Factory Call: ${statusToText[await depositStatus.l2ForwarderFactory.status()]}`) - console.log(`L2 to L3 Token Bridge: ${statusToText[await depositStatus.l2l3TokenBridge?.status() || L1ToL2MessageStatus.NOT_YET_CREATED]}`) + const depositStatus = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider }) + console.log(`L1 to L2 Fee Token Bridge: ${statusToText[await depositStatus.l1l2FeeTokenBridgeRetryable?.status() || 'NA']}`) + console.log(`L1 to L2 Token Bridge: ${statusToText[await depositStatus.l1l2TokenBridgeRetryable.status()]}`) + console.log(`L2 Forwarder Factory Call: ${statusToText[await depositStatus.l2ForwarderFactoryRetryable.status()]}`) + console.log(`L2 to L3 Token Bridge: ${statusToText[await depositStatus.l2l3TokenBridgeRetryable?.status() || L1ToL2MessageStatus.NOT_YET_CREATED]}`) console.log(`Completed: ${depositStatus.completed}`) + console.log(`L2 Forwarder Factory Call Frontran: ${depositStatus.l2ForwarderFactoryRetryableFrontRan}`) } if (!process.argv[2]) { diff --git a/packages/l1-l3-teleport/scripts/monitor-eth-deposit-status.ts b/packages/l1-l3-teleport/scripts/monitor-eth-deposit-status.ts index 288d43b..8b78dfc 100644 --- a/packages/l1-l3-teleport/scripts/monitor-eth-deposit-status.ts +++ b/packages/l1-l3-teleport/scripts/monitor-eth-deposit-status.ts @@ -1,6 +1,6 @@ import { providers } from "ethers" import { arbLog, requireEnvVariables } from 'arb-shared-dependencies' -import { Erc20L1L3Bridger, EthL1L3Bridger, L1ToL2MessageStatus, getL2Network } from "godzillaba-arbitrum-sdk" +import { EthL1L3Bridger, L1ToL2MessageStatus, getL2Network } from "@arbitrum/sdk" // Importing configuration // require('dotenv').config() @@ -33,9 +33,7 @@ const main = async (txHash: string) => { * Get deposit status */ console.log('Getting deposit status...') - const depositStatus = await bridger.getDepositMessages({ txHash, l1Provider, l2Provider, l3Provider }) - - + const depositStatus = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider }) /** * If any of these retryables fail (i.e. FUNDS_DEPOSITED_ON_L2), manually redeem them in the order displayed below diff --git a/packages/l1-l3-teleport/scripts/recover-deposit.ts b/packages/l1-l3-teleport/scripts/recover-deposit.ts new file mode 100644 index 0000000..fc2eab3 --- /dev/null +++ b/packages/l1-l3-teleport/scripts/recover-deposit.ts @@ -0,0 +1,93 @@ +import { providers, Wallet } from "ethers" +import { arbLog, requireEnvVariables } from 'arb-shared-dependencies' +import { Erc20L1L3Bridger, L1ToL2MessageReader, L1ToL2MessageStatus, L1ToL2MessageWriter, getL2Network } from "@arbitrum/sdk" + +// Importing configuration // +require('dotenv').config() +requireEnvVariables(['L1RPC', 'L2RPC', 'L3RPC', 'DEVNET_PRIVKEY']) + +// Initial setup // +const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) +const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) +const l3Provider = new providers.JsonRpcProvider(process.env.L3RPC) + +const l2Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l2Provider) +const l3Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l3Provider) + +const main = async (txHash: string) => { + await arbLog(`Recovering a failed deposit`) + + /** + * Use L3 Network to initialize a bridger + */ + const l3Network = await getL2Network(l3Provider) + const bridger = new Erc20L1L3Bridger(l3Network) + + const status = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider }) + + if (status.completed) { + console.log('Teleportation already completed') + return + } + + if (status.l2ForwarderFactoryRetryableFrontRan) { + console.log('L2 Forwarder Factory Call was frontran, funds were picked up by another teleportation that may or may not have completed.') + console.log('Please check the status of all teleportations that share the same L2Forwarder and token') + console.log('If all those teleportations have (status.completed || status.l2ForwarderFactoryRetryableFrontRan), then all deposits have safely arrived on L2') + return + } + + const getWriter = (signer: Wallet, msg: L1ToL2MessageReader) => { + return new L1ToL2MessageWriter(signer, msg.chainId, msg.sender, msg.messageNumber, msg.l1BaseFee, msg.messageData) + } + + // the order of check / redeem is important + + if (await status.l1l2FeeTokenBridgeRetryable?.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + console.log('Redeeming l1l2FeeTokenBridgeRetryable...') + await getWriter(l2Signer, status.l1l2FeeTokenBridgeRetryable!).redeem() + console.log('Redeemed l1l2FeeTokenBridgeRetryable') + } + + if (await status.l1l2TokenBridgeRetryable.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + console.log('Redeeming l1l2TokenBridgeRetryable...') + await getWriter(l2Signer, status.l1l2TokenBridgeRetryable).redeem() + console.log('Redeemed l1l2TokenBridgeRetryable') + } + + if (await status.l2ForwarderFactoryRetryable.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + console.log('Redeeming l2ForwarderFactoryRetryable...') + + try { + await getWriter(l2Signer, status.l2ForwarderFactoryRetryable).redeem() + } catch (e) { + console.error(e) + console.log('Failed to redeem l2ForwarderFactoryRetryable') + console.log('If the l2ForwarderFactoryRetryable cannot be redeemed, the teleportation is stuck') + console.log('The sender of the teleport must call L2Forwarder::rescue to recover the funds') + console.log('If the sender of the teleport is a contract on L1, this needs to be done via a L1->L2 retryable') + console.log('If the sender of the teleport is an EOA, this can be done via a L2 transaction') + return + } + console.log('Redeemed l2ForwarderFactoryRetryable') + + console.log('l2l3TokenBridgeRetryable should be created after redeeming l2ForwarderFactoryRetryable') + console.log('Please run this script again to redeem l2l3TokenBridgeRetryable if it fails to redeem automatically') + } + + if (await status.l2l3TokenBridgeRetryable?.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + console.log('Redeeming l2l3TokenBridgeRetryable...') + // note that the l3 signer is used here, l2 signer used elsewhere + await getWriter(l3Signer, status.l2l3TokenBridgeRetryable!).redeem() + console.log('Redeemed l2l3TokenBridgeRetryable') + + console.log('All retryables redeemed, teleportation completed!') + } +} + +if (!process.argv[2]) { + console.error('Please provide a transaction hash') + process.exit(1) +} + +main(process.argv[2]) \ No newline at end of file diff --git a/packages/l1-l3-teleport/scripts/recover-eth-deposit.ts b/packages/l1-l3-teleport/scripts/recover-eth-deposit.ts new file mode 100644 index 0000000..1e90fda --- /dev/null +++ b/packages/l1-l3-teleport/scripts/recover-eth-deposit.ts @@ -0,0 +1,61 @@ +import { providers, Wallet } from "ethers" +import { arbLog, requireEnvVariables } from 'arb-shared-dependencies' +import { Erc20L1L3Bridger, EthL1L3Bridger, L1ToL2MessageReader, L1ToL2MessageStatus, L1ToL2MessageWriter, getL2Network } from "@arbitrum/sdk" + +// Importing configuration // +require('dotenv').config() +requireEnvVariables(['L1RPC', 'L2RPC', 'L3RPC', 'DEVNET_PRIVKEY']) + +// Initial setup // +const l1Provider = new providers.JsonRpcProvider(process.env.L1RPC) +const l2Provider = new providers.JsonRpcProvider(process.env.L2RPC) +const l3Provider = new providers.JsonRpcProvider(process.env.L3RPC) + +const l2Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l2Provider) +const l3Signer = new Wallet(process.env.DEVNET_PRIVKEY!, l3Provider) + +const main = async (txHash: string) => { + await arbLog(`Recovering a failed ETH deposit`) + + /** + * Use L3 Network to initialize a bridger + */ + const l3Network = await getL2Network(l3Provider) + const bridger = new EthL1L3Bridger(l3Network) + + const status = await bridger.getDepositStatus({ txHash, l1Provider, l2Provider, l3Provider }) + + if (status.completed) { + console.log('Teleportation already completed') + return + } + + const getWriter = (signer: Wallet, msg: L1ToL2MessageReader) => { + return new L1ToL2MessageWriter(signer, msg.chainId, msg.sender, msg.messageNumber, msg.l1BaseFee, msg.messageData) + } + + // the order of check / redeem is important + + if (await status.l2Retryable.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + console.log('Redeeming l2Retryable...') + await getWriter(l2Signer, status.l2Retryable).redeem() + console.log('Redeemed l2Retryable') + + console.log('l3Retryable should be created after redeeming l2Retryable') + console.log('Please run this script again to redeem l3Retryable if it fails to redeem automatically') + } + + if (await status.l3Retryable?.status() === L1ToL2MessageStatus.FUNDS_DEPOSITED_ON_L2) { + console.log('Redeeming l3Retryable...') + await getWriter(l3Signer, status.l3Retryable!).redeem() + console.log('Redeemed l3Retryable') + console.log('All retryables redeemed, teleportation completed!') + } +} + +if (!process.argv[2]) { + console.error('Please provide a transaction hash') + process.exit(1) +} + +main(process.argv[2]) \ No newline at end of file diff --git a/yarn.lock b/yarn.lock index fc660ba..5d52baf 100644 --- a/yarn.lock +++ b/yarn.lock @@ -18,6 +18,17 @@ optionalDependencies: sol2uml "2.2.0" +"@arbitrum/sdk@3.5.1-teleporter.0": + version "3.5.1-teleporter.0" + resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.5.1-teleporter.0.tgz#68e2612cdaec43b173ee4dbb928871cca067c50f" + integrity sha512-0lJrUgQr/nTEbMzOKgl70m/1vnvVWE8WTOG+UFBN3iSMugcxGsJcNHh96xQV3qyYbo4XcWLwrRkwGg2WVuN25w== + dependencies: + "@ethersproject/address" "^5.0.8" + "@ethersproject/bignumber" "^5.1.1" + "@ethersproject/bytes" "^5.0.8" + async-mutex "^0.4.0" + ethers "^5.1.0" + "@arbitrum/sdk@^v3.1.9": version "3.1.11" resolved "https://registry.yarnpkg.com/@arbitrum/sdk/-/sdk-3.1.11.tgz#14fa67daceef7568b949e4a2695d561dd47eec5b" @@ -4823,17 +4834,6 @@ globalthis@^1.0.3: dependencies: define-properties "^1.1.3" -godzillaba-arbitrum-sdk@^3.3.3-teleport: - version "3.3.3-teleport" - resolved "https://registry.yarnpkg.com/godzillaba-arbitrum-sdk/-/godzillaba-arbitrum-sdk-3.3.3-teleport.tgz#40786631068327231633738bfdf8e6a4a8c08281" - integrity sha512-gmzKsey8Kumfx7zieFbDZIXAndVQX4HxNq9/qXthVPx9pXy7E1rKIqEtDcaID74ixGUkKTjkHqeszJ6bWk6uJQ== - dependencies: - "@ethersproject/address" "^5.0.8" - "@ethersproject/bignumber" "^5.1.1" - "@ethersproject/bytes" "^5.0.8" - async-mutex "^0.4.0" - ethers "^5.1.0" - gopd@^1.0.1: version "1.0.1" resolved "https://registry.yarnpkg.com/gopd/-/gopd-1.0.1.tgz#29ff76de69dac7489b7c0918a5788e56477c332c"