diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts index 09d126e0fe..42b7ae8d6e 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/CctpTransferStarter.ts @@ -11,7 +11,11 @@ import { } from './BridgeTransferStarter' import { formatAmount } from '../util/NumberUtils' import { fetchPerMessageBurnLimit, getCctpContracts } from './cctp' -import { getChainIdFromProvider, getAddressFromSigner } from './utils' +import { + getChainIdFromProvider, + getAddressFromSigner, + validateSignerChainId +} from './utils' import { fetchErc20Allowance } from '../util/TokenUtils' import { TokenMessengerAbi } from '../util/cctp/TokenMessengerAbi' import { Address } from '../util/AddressUtils' @@ -84,9 +88,13 @@ export class CctpTransferStarter extends BridgeTransferStarter { } public async transfer({ signer, amount, destinationAddress }: TransferProps) { + const address = await getAddressFromSigner(signer) const sourceChainId = await getChainIdFromProvider(this.sourceChainProvider) - const address = await getAddressFromSigner(signer) + await validateSignerChainId({ + signer, + sourceChainIdOrProvider: sourceChainId + }) // cctp has an upper limit for transfer const burnLimit = await fetchPerMessageBurnLimit({ diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts index a5f5200cbe..3aa9c2aa33 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20DepositStarter.ts @@ -1,5 +1,6 @@ import { Erc20Bridger, + getArbitrumNetwork, scaleFrom18DecimalsToNativeTokenDecimals } from '@arbitrum/sdk' import { BigNumber, constants, utils } from 'ethers' @@ -20,8 +21,13 @@ import { fetchErc20Allowance, fetchErc20ParentChainGatewayAddress } from '../util/TokenUtils' -import { getAddressFromSigner, percentIncrease } from './utils' +import { + getAddressFromSigner, + percentIncrease, + validateSignerChainId +} from './utils' import { depositTokenEstimateGas } from '../util/TokenDepositUtils' +import { addressIsSmartContract } from '../util/AddressUtils' // https://github.com/OffchainLabs/arbitrum-sdk/blob/main/src/lib/message/L1ToL2MessageGasEstimator.ts#L33 export const DEFAULT_GAS_PRICE_PERCENT_INCREASE = BigNumber.from(500) @@ -297,6 +303,14 @@ export class Erc20DepositStarter extends BridgeTransferStarter { const address = await getAddressFromSigner(signer) const erc20Bridger = await this.getBridger() + const destinationChainId = ( + await this.destinationChainProvider.getNetwork() + ).chainId + + await validateSignerChainId({ + signer, + sourceChainIdOrProvider: this.sourceChainProvider + }) const depositRequest = await erc20Bridger.getDepositRequest({ parentProvider: this.sourceChainProvider, @@ -313,6 +327,25 @@ export class Erc20DepositStarter extends BridgeTransferStarter { ...overrides }) + const depositToAddress = depositRequest.txRequest.to.toLowerCase() + + if (!addressIsSmartContract(depositToAddress, this.sourceChainProvider)) { + throw new Error( + `Parent chain token gateway router address provided is not a smart contract address.` + ) + } + + const parentGatewayRouterAddressForChain = + getArbitrumNetwork( + destinationChainId + ).tokenBridge?.parentGatewayRouter.toLowerCase() + + if (depositToAddress !== parentGatewayRouterAddressForChain) { + throw new Error( + `Wrong token gateway router address on parent chain. Expected ${parentGatewayRouterAddressForChain}, got ${depositToAddress} instead.` + ) + } + const gasLimit = await this.sourceChainProvider.estimateGas( depositRequest.txRequest ) diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20TeleportStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20TeleportStarter.ts index f4fee31dbb..14619e6348 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20TeleportStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20TeleportStarter.ts @@ -12,8 +12,13 @@ import { TransferType } from './BridgeTransferStarter' import { fetchErc20Allowance } from '../util/TokenUtils' -import { getAddressFromSigner, percentIncrease } from './utils' +import { + getAddressFromSigner, + percentIncrease, + validateSignerChainId +} from './utils' import { getL2ConfigForTeleport } from './teleport' +import { addressIsSmartContract } from '../util/AddressUtils' export class Erc20TeleportStarter extends BridgeTransferStarter { public transferType: TransferType = 'erc20_teleport' @@ -175,6 +180,11 @@ export class Erc20TeleportStarter extends BridgeTransferStarter { const address = await getAddressFromSigner(signer) + await validateSignerChainId({ + signer, + sourceChainIdOrProvider: this.sourceChainProvider + }) + const l2Provider = await this.getL2Provider() const l1l3Bridger = await this.getBridger() @@ -189,6 +199,23 @@ export class Erc20TeleportStarter extends BridgeTransferStarter { l3Provider: this.destinationChainProvider }) + const depositToAddress = depositRequest.txRequest.to.toLowerCase() + + if (!addressIsSmartContract(depositToAddress, this.sourceChainProvider)) { + throw new Error( + `Teleporter transfer address provided is not a smart contract address.` + ) + } + + const l1TeleporterAddress = + l1l3Bridger.l2Network.teleporter?.l1Teleporter.toLowerCase() + + if (depositToAddress !== l1TeleporterAddress) { + throw new Error( + `Wrong address for teleporter transfer to destination chain. Expected ${l1TeleporterAddress}, got ${depositToAddress} instead.` + ) + } + const tx = await l1l3Bridger.deposit({ txRequest: depositRequest.txRequest, l1Signer: signer diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20WithdrawalStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20WithdrawalStarter.ts index 8ee9f51ac2..5f40a3d411 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20WithdrawalStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/Erc20WithdrawalStarter.ts @@ -1,4 +1,4 @@ -import { Erc20Bridger } from '@arbitrum/sdk' +import { Erc20Bridger, getArbitrumNetwork } from '@arbitrum/sdk' import { BigNumber, constants } from 'ethers' import { ERC20__factory } from '@arbitrum/sdk/dist/lib/abi/factories/ERC20__factory' import { @@ -17,7 +17,8 @@ import { import { getAddressFromSigner, getChainIdFromProvider, - percentIncrease + percentIncrease, + validateSignerChainId } from './utils' import { tokenRequiresApprovalOnL2 } from '../util/L2ApprovalUtils' import { withdrawInitTxEstimateGas } from '../util/WithdrawalUtils' @@ -188,6 +189,13 @@ export class Erc20WithdrawalStarter extends BridgeTransferStarter { } public async transfer({ amount, signer, destinationAddress }: TransferProps) { + const sourceChainId = await getChainIdFromProvider(this.sourceChainProvider) + + await validateSignerChainId({ + signer, + sourceChainIdOrProvider: sourceChainId + }) + if (!this.sourceChainErc20Address) { throw Error('Erc20 token address not found') } @@ -197,8 +205,6 @@ export class Erc20WithdrawalStarter extends BridgeTransferStarter { const address = await getAddressFromSigner(signer) - const sourceChainId = await getChainIdFromProvider(this.sourceChainProvider) - const isSmartContractWallet = await addressIsSmartContract( address, sourceChainId @@ -219,6 +225,25 @@ export class Erc20WithdrawalStarter extends BridgeTransferStarter { amount }) + const withdrawToAddress = request.txRequest.to.toLowerCase() + + if (!addressIsSmartContract(withdrawToAddress, this.sourceChainProvider)) { + throw new Error( + `Child chain token gateway router address provided is not a smart contract address.` + ) + } + + const childGatewayRouterAddressForChain = + getArbitrumNetwork( + sourceChainId + ).tokenBridge?.childGatewayRouter.toLowerCase() + + if (withdrawToAddress !== childGatewayRouterAddressForChain) { + throw new Error( + `Wrong token gateway router address on child chain. Expected ${childGatewayRouterAddressForChain}, got ${withdrawToAddress} instead.` + ) + } + const tx = await erc20Bridger.withdraw({ ...request, childSigner: signer, diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts index 54d636aaaf..dca9a7fc76 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthDepositStarter.ts @@ -1,5 +1,6 @@ import { EthBridger, + getArbitrumNetwork, scaleFrom18DecimalsToNativeTokenDecimals } from '@arbitrum/sdk' import { BigNumber, Signer } from 'ethers' @@ -12,10 +13,15 @@ import { TransferProps, TransferType } from './BridgeTransferStarter' -import { getAddressFromSigner, percentIncrease } from './utils' +import { + getAddressFromSigner, + percentIncrease, + validateSignerChainId +} from './utils' import { depositEthEstimateGas } from '../util/EthDepositUtils' import { fetchErc20Allowance } from '../util/TokenUtils' import { isCustomDestinationAddressTx } from '../state/app/utils' +import { addressIsSmartContract } from '../util/AddressUtils' import { DEFAULT_GAS_PRICE_PERCENT_INCREASE } from './Erc20DepositStarter' import { fetchNativeCurrency } from '../hooks/useNativeCurrency' @@ -170,12 +176,20 @@ export class EthDepositStarter extends BridgeTransferStarter { public async transfer({ amount, signer, destinationAddress }: TransferProps) { const address = await getAddressFromSigner(signer) const ethBridger = await this.getBridger() + const destinationChainId = ( + await this.destinationChainProvider.getNetwork() + ).chainId const isDifferentDestinationAddress = isCustomDestinationAddressTx({ sender: address, destination: destinationAddress }) + await validateSignerChainId({ + signer, + sourceChainIdOrProvider: this.sourceChainProvider + }) + const depositRequest = isDifferentDestinationAddress ? await ethBridger.getDepositToRequest({ amount, @@ -190,6 +204,21 @@ export class EthDepositStarter extends BridgeTransferStarter { from: address }) + const depositToAddress = depositRequest.txRequest.to.toLowerCase() + + if (!addressIsSmartContract(depositToAddress, this.sourceChainProvider)) { + throw new Error(`Inbox address provided is not a smart contract address.`) + } + + const inboxAddressForChain = + getArbitrumNetwork(destinationChainId).ethBridge.inbox.toLowerCase() + + if (depositToAddress !== inboxAddressForChain) { + throw new Error( + `Wrong inbox address for destination chain. Expected ${inboxAddressForChain}, got ${depositToAddress} instead.` + ) + } + const gasLimit = await this.sourceChainProvider.estimateGas( depositRequest.txRequest ) diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthTeleportStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthTeleportStarter.ts index 57cf90756e..c1bb23cdb3 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthTeleportStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthTeleportStarter.ts @@ -9,7 +9,12 @@ import { TransferType } from './BridgeTransferStarter' import { getL2ConfigForTeleport } from './teleport' -import { getAddressFromSigner, percentIncrease } from './utils' +import { + getAddressFromSigner, + percentIncrease, + validateSignerChainId +} from './utils' +import { addressIsSmartContract } from '../util/AddressUtils' export class EthTeleportStarter extends BridgeTransferStarter { public transferType: TransferType = 'eth_teleport' @@ -107,6 +112,11 @@ export class EthTeleportStarter extends BridgeTransferStarter { public async transfer({ amount, signer }: TransferProps) { const address = await getAddressFromSigner(signer) + await validateSignerChainId({ + signer, + sourceChainIdOrProvider: this.sourceChainProvider + }) + const l2Provider = await this.getL2Provider() const l1l3Bridger = await this.getBridger() @@ -120,6 +130,21 @@ export class EthTeleportStarter extends BridgeTransferStarter { l3Provider: this.destinationChainProvider }) + const depositToAddress = depositRequest.txRequest.to.toLowerCase() + + if (!addressIsSmartContract(depositToAddress, this.sourceChainProvider)) { + throw new Error(`Inbox address provided is not a smart contract address.`) + } + + const inboxAddressForChain = + l1l3Bridger.l2Network.ethBridge.inbox.toLowerCase() + + if (depositToAddress !== inboxAddressForChain) { + throw new Error( + `Wrong inbox address for teleporter transfer to destination chain. Expected ${inboxAddressForChain}, got ${depositToAddress} instead.` + ) + } + const tx = await l1l3Bridger.deposit({ ...depositRequest, l1Signer: signer diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthWithdrawalStarter.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthWithdrawalStarter.ts index a759e0d625..aca8f07824 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthWithdrawalStarter.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/EthWithdrawalStarter.ts @@ -1,13 +1,19 @@ import { BigNumber } from 'ethers' import { EthBridger } from '@arbitrum/sdk' +import { ARB_SYS_ADDRESS } from '@arbitrum/sdk/dist/lib/dataEntities/constants' import { BridgeTransferStarter, TransferEstimateGas, TransferProps, TransferType } from './BridgeTransferStarter' -import { getAddressFromSigner, percentIncrease } from './utils' +import { + getAddressFromSigner, + percentIncrease, + validateSignerChainId +} from './utils' import { withdrawInitTxEstimateGas } from '../util/WithdrawalUtils' +import { addressIsSmartContract } from '../util/AddressUtils' export class EthWithdrawalStarter extends BridgeTransferStarter { public transferType: TransferType = 'eth_withdrawal' @@ -49,12 +55,31 @@ export class EthWithdrawalStarter extends BridgeTransferStarter { const address = await getAddressFromSigner(signer) const ethBridger = await EthBridger.fromProvider(this.sourceChainProvider) + await validateSignerChainId({ + signer, + sourceChainIdOrProvider: this.sourceChainProvider + }) + const request = await ethBridger.getWithdrawalRequest({ amount, destinationAddress: destinationAddress ?? address, from: address }) + const withdrawToAddress = request.txRequest.to + + if (!addressIsSmartContract(withdrawToAddress, this.sourceChainProvider)) { + throw new Error( + `Native currency withdrawal request address is not a smart contract address.` + ) + } + + if (withdrawToAddress.toLowerCase() !== ARB_SYS_ADDRESS.toLowerCase()) { + throw new Error( + `Native currency withdrawal request address must be the ArbSys address ${ARB_SYS_ADDRESS} instead of ${withdrawToAddress}.` + ) + } + const tx = await ethBridger.withdraw({ ...request, childSigner: signer, diff --git a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts index 54e7483bbf..97617759fb 100644 --- a/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts +++ b/packages/arb-token-bridge-ui/src/token-bridge-sdk/utils.ts @@ -120,3 +120,24 @@ export function getProviderForChainId(chainId: ChainId): StaticJsonRpcProvider { return createProviderWithCache(chainId) } + +export async function validateSignerChainId({ + signer, + sourceChainIdOrProvider +}: { + signer: Signer + sourceChainIdOrProvider: number | Provider +}) { + const signerChainId = await signer.getChainId() + + const sourceChainId = + typeof sourceChainIdOrProvider === 'number' + ? sourceChainIdOrProvider + : await getChainIdFromProvider(sourceChainIdOrProvider) + + if (signerChainId !== sourceChainId) { + throw new Error( + `Signer is on chain ${signerChainId} but should be on chain ${sourceChainId}. Please try again after connecting to the correct chain in your wallet.` + ) + } +} diff --git a/packages/arb-token-bridge-ui/src/util/AddressUtils.ts b/packages/arb-token-bridge-ui/src/util/AddressUtils.ts index 9e15ef0750..a97a37f6ec 100644 --- a/packages/arb-token-bridge-ui/src/util/AddressUtils.ts +++ b/packages/arb-token-bridge-ui/src/util/AddressUtils.ts @@ -5,8 +5,15 @@ import { getProviderForChainId } from '../token-bridge-sdk/utils' export type Address = `0x${string}` -export async function addressIsSmartContract(address: string, chainId: number) { - const provider = getProviderForChainId(chainId) +export async function addressIsSmartContract( + address: string, + chainIdOrProvider: number | Provider +) { + const provider = + typeof chainIdOrProvider === 'number' + ? getProviderForChainId(chainIdOrProvider) + : chainIdOrProvider + try { return (await provider.getCode(address)).length > 2 } catch (_) {