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: validate addresses during tx broadcast #5440

Merged
merged 12 commits into from
Oct 10, 2023
2 changes: 0 additions & 2 deletions .env.base
Original file line number Diff line number Diff line change
Expand Up @@ -159,5 +159,3 @@ REACT_APP_EXPERIMENTAL_MM_SNAPPY_FINGERS=true

# Experemental features (not production ready)
REACT_APP_EXPERIMENTAL_CUSTOM_SEND_NONCE=false

REACT_APP_TRM_LABS_API_URL=https://api.trmlabs.com/public/v1/sanctions/screening
gomesalexandre marked this conversation as resolved.
Show resolved Hide resolved
6 changes: 4 additions & 2 deletions packages/chain-adapters/src/api.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,11 +3,13 @@ import type { BIP44Params, UtxoAccountType } from '@shapeshiftoss/types'

import type {
Account,
BroadcastTransactionInput,
BuildSendTxInput,
FeeDataEstimate,
GetAddressInput,
GetBIP44ParamsInput,
GetFeeDataInput,
SignAndBroadcastTransactionInput,
SignTx,
SignTxInput,
SubscribeError,
Expand Down Expand Up @@ -65,11 +67,11 @@ export type ChainAdapter<T extends ChainId> = {

signTransaction(signTxInput: SignTxInput<SignTx<T>>): Promise<string>

signAndBroadcastTransaction?(signTxInput: SignTxInput<SignTx<T>>): Promise<string>
signAndBroadcastTransaction?(input: SignAndBroadcastTransactionInput<T>): Promise<string>

getFeeData(input: Partial<GetFeeDataInput<T>>): Promise<FeeDataEstimate<T>>

broadcastTransaction(hex: string): Promise<string>
broadcastTransaction(input: BroadcastTransactionInput): Promise<string>

validateAddress(address: string): Promise<ValidAddressResult>

Expand Down
16 changes: 14 additions & 2 deletions packages/chain-adapters/src/cosmossdk/CosmosSdkBaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,12 +17,14 @@ import type { ChainAdapter as IChainAdapter } from '../api'
import { ErrorHandler } from '../error/ErrorHandler'
import type {
Account,
BroadcastTransactionInput,
BuildSendApiTxInput,
BuildSendTxInput,
FeeDataEstimate,
GetAddressInput,
GetBIP44ParamsInput,
GetFeeDataInput,
SignAndBroadcastTransactionInput,
SignTx,
SignTxInput,
SubscribeError,
Expand All @@ -35,6 +37,7 @@ import type {
import { ValidAddressResultType } from '../types'
import { toAddressNList, toRootDerivationPath } from '../utils'
import { bnOrZero } from '../utils/bignumber'
import { validateAddress } from '../utils/validateAddress'
import type { cosmos, thorchain } from './'
import type {
BuildTransactionInput,
Expand Down Expand Up @@ -152,7 +155,7 @@ export abstract class CosmosSdkBaseAdapter<T extends CosmosSdkChainId> implement
abstract getAddress(input: GetAddressInput): Promise<string>
abstract getFeeData(input: Partial<GetFeeDataInput<T>>): Promise<FeeDataEstimate<T>>
abstract signTransaction(signTxInput: SignTxInput<SignTx<T>>): Promise<string>
abstract signAndBroadcastTransaction(signTxInput: SignTxInput<SignTx<T>>): Promise<string>
abstract signAndBroadcastTransaction(input: SignAndBroadcastTransactionInput<T>): Promise<string>

getChainId(): ChainId {
return this.chainId
Expand Down Expand Up @@ -329,7 +332,16 @@ export abstract class CosmosSdkBaseAdapter<T extends CosmosSdkChainId> implement
return { txToSign }
}

broadcastTransaction(hex: string): Promise<string> {
async broadcastTransaction({
senderAddress,
receiverAddress,
hex,
}: BroadcastTransactionInput): Promise<string> {
await Promise.all([
validateAddress(senderAddress),
gomesalexandre marked this conversation as resolved.
Show resolved Hide resolved
receiverAddress !== undefined && validateAddress(receiverAddress),
])

try {
return this.providers.http.sendTx({ body: { rawTx: hex } })
} catch (err) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -17,10 +17,12 @@ import type {
FeeDataEstimate,
GetAddressInput,
GetFeeDataInput,
SignAndBroadcastTransactionInput,
SignTxInput,
} from '../../types'
import { ChainAdapterDisplayName } from '../../types'
import { bn, calcFee, toAddressNList } from '../../utils'
import { validateAddress } from '../../utils/validateAddress'
import type { ChainAdapterArgs } from '../CosmosSdkBaseAdapter'
import { assertIsValidatorAddress, CosmosSdkBaseAdapter } from '../CosmosSdkBaseAdapter'
import type { Message, ValidatorAction } from '../types'
Expand Down Expand Up @@ -280,7 +282,16 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.CosmosMainn
}
}

async signAndBroadcastTransaction(signTxInput: SignTxInput<CosmosSignTx>): Promise<string> {
async signAndBroadcastTransaction({
senderAddress,
receiverAddress,
signTxInput,
}: SignAndBroadcastTransactionInput<KnownChainIds.CosmosMainnet>): Promise<string> {
await Promise.all([
validateAddress(senderAddress),
receiverAddress !== undefined && validateAddress(receiverAddress),
])

const { wallet } = signTxInput
try {
if (supportsCosmos(wallet)) {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -14,11 +14,13 @@ import type {
FeeDataEstimate,
GetAddressInput,
GetFeeDataInput,
SignAndBroadcastTransactionInput,
SignTxInput,
} from '../../types'
import { ChainAdapterDisplayName } from '../../types'
import { toAddressNList } from '../../utils'
import { bnOrZero } from '../../utils/bignumber'
import { validateAddress } from '../../utils/validateAddress'
import type { ChainAdapterArgs } from '../CosmosSdkBaseAdapter'
import { CosmosSdkBaseAdapter } from '../CosmosSdkBaseAdapter'
import type { Message } from '../types'
Expand Down Expand Up @@ -204,8 +206,18 @@ export class ChainAdapter extends CosmosSdkBaseAdapter<KnownChainIds.ThorchainMa
}
}

async signAndBroadcastTransaction(signTxInput: SignTxInput<ThorchainSignTx>): Promise<string> {
async signAndBroadcastTransaction({
senderAddress,
receiverAddress,
signTxInput,
}: SignAndBroadcastTransactionInput<KnownChainIds.ThorchainMainnet>): Promise<string> {
await Promise.all([
validateAddress(senderAddress),
receiverAddress !== undefined && validateAddress(receiverAddress),
])

const { wallet } = signTxInput

try {
if (supportsThorchain(wallet)) {
const signedTx = await this.signTransaction(signTxInput)
Expand Down
24 changes: 22 additions & 2 deletions packages/chain-adapters/src/evm/EvmBaseAdapter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -27,12 +27,14 @@ import type { ChainAdapter as IChainAdapter } from '../api'
import { ErrorHandler } from '../error/ErrorHandler'
import type {
Account,
BroadcastTransactionInput,
BuildSendApiTxInput,
BuildSendTxInput,
FeeDataEstimate,
GetAddressInput,
GetBIP44ParamsInput,
GetFeeDataInput,
SignAndBroadcastTransactionInput,
SignMessageInput,
SignTx,
SignTxInput,
Expand All @@ -47,6 +49,7 @@ import type {
import { ValidAddressResultType } from '../types'
import { getAssetNamespace, toAddressNList, toRootDerivationPath } from '../utils'
import { bnOrZero } from '../utils/bignumber'
import { validateAddress } from '../utils/validateAddress'
import type { arbitrum, avalanche, bnbsmartchain, ethereum, gnosis, optimism, polygon } from '.'
import type {
BuildCustomApiTxInput,
Expand Down Expand Up @@ -442,7 +445,16 @@ export abstract class EvmBaseAdapter<T extends EvmChainId> implements IChainAdap
}
}

async signAndBroadcastTransaction(signTxInput: SignTxInput<ETHSignTx>): Promise<string> {
async signAndBroadcastTransaction({
senderAddress,
receiverAddress,
signTxInput,
}: SignAndBroadcastTransactionInput<T>): Promise<string> {
await Promise.all([
validateAddress(senderAddress),
receiverAddress !== undefined && validateAddress(receiverAddress),
])

try {
const { txToSign, wallet } = signTxInput

Expand All @@ -461,7 +473,15 @@ export abstract class EvmBaseAdapter<T extends EvmChainId> implements IChainAdap
}
}

broadcastTransaction(hex: string): Promise<string> {
async broadcastTransaction({
senderAddress,
receiverAddress,
hex,
}: BroadcastTransactionInput): Promise<string> {
await Promise.all([
validateAddress(senderAddress),
receiverAddress !== undefined && validateAddress(receiverAddress),
])
return this.providers.http.sendTx({ sendTxBody: { hex } })
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import { bn } from '../../utils/bignumber'
import type { ChainAdapterArgs, EvmChainId } from '../EvmBaseAdapter'
import * as arbitrum from './ArbitrumChainAdapter'

jest.mock('../../utils/validateAddress', () => ({
validateAddress: jest.fn(),
}))

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const EOA_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'

Expand Down Expand Up @@ -318,11 +322,15 @@ describe('ArbitrumChainAdapter', () => {

wallet.ethSendTx = async () => await Promise.resolve(null)

const tx = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>
const signTxInput = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>

await expect(adapter.signAndBroadcastTransaction(tx)).rejects.toThrow(
/Error signing & broadcasting tx/,
)
await expect(
adapter.signAndBroadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
signTxInput,
}),
).rejects.toThrow(/Error signing & broadcasting tx/)
})

it('should return the hash returned by wallet.ethSendTx', async () => {
Expand All @@ -334,11 +342,15 @@ describe('ArbitrumChainAdapter', () => {
hash: '0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331',
})

const tx = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>
const signTxInput = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>

await expect(adapter.signAndBroadcastTransaction(tx)).resolves.toEqual(
'0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331',
)
await expect(
adapter.signAndBroadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
signTxInput,
}),
).resolves.toEqual('0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331')
})
})

Expand Down Expand Up @@ -392,7 +404,11 @@ describe('ArbitrumChainAdapter', () => {
const adapter = new arbitrum.ChainAdapter(args)

const mockTx = '0x123'
const result = await adapter.broadcastTransaction(mockTx)
const result = await adapter.broadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
hex: mockTx,
})

expect(args.providers.http.sendTx).toHaveBeenCalledWith<any>({ sendTxBody: { hex: mockTx } })
expect(result).toEqual(expectedResult)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,10 @@ import { bn } from '../../utils/bignumber'
import type { ChainAdapterArgs, EvmChainId } from '../EvmBaseAdapter'
import * as avalanche from './AvalancheChainAdapter'

jest.mock('../../utils/validateAddress', () => ({
validateAddress: jest.fn(),
}))

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const EOA_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'

Expand Down Expand Up @@ -323,11 +327,15 @@ describe('AvalancheChainAdapter', () => {

wallet.ethSendTx = async () => await Promise.resolve(null)

const tx = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>
const signTxInput = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>

await expect(adapter.signAndBroadcastTransaction(tx)).rejects.toThrow(
/Error signing & broadcasting tx/,
)
await expect(
adapter.signAndBroadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
signTxInput,
}),
).rejects.toThrow(/Error signing & broadcasting tx/)
})

it('should return the hash returned by wallet.ethSendTx', async () => {
Expand All @@ -339,11 +347,15 @@ describe('AvalancheChainAdapter', () => {
hash: '0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331',
})

const tx = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>
const signTxInput = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>

await expect(adapter.signAndBroadcastTransaction(tx)).resolves.toEqual(
'0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331',
)
await expect(
adapter.signAndBroadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
signTxInput,
}),
).resolves.toEqual('0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331')
})
})

Expand Down Expand Up @@ -397,7 +409,11 @@ describe('AvalancheChainAdapter', () => {
const adapter = new avalanche.ChainAdapter(args)

const mockTx = '0x123'
const result = await adapter.broadcastTransaction(mockTx)
const result = await adapter.broadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
hex: mockTx,
})

expect(args.providers.http.sendTx).toHaveBeenCalledWith<any>({ sendTxBody: { hex: mockTx } })
expect(result).toEqual(expectedResult)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -21,6 +21,10 @@ import { bn } from '../../utils/bignumber'
import type { ChainAdapterArgs, EvmChainId } from '../EvmBaseAdapter'
import * as bsc from './BscChainAdapter'

jest.mock('../../utils/validateAddress', () => ({
validateAddress: jest.fn(),
}))

const ZERO_ADDRESS = '0x0000000000000000000000000000000000000000'
const EOA_ADDRESS = '0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045'

Expand Down Expand Up @@ -300,11 +304,15 @@ describe('BscChainAdapter', () => {

wallet.ethSendTx = async () => await Promise.resolve(null)

const tx = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>
const signTxInput = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>

await expect(adapter.signAndBroadcastTransaction(tx)).rejects.toThrow(
/Error signing & broadcasting tx/,
)
await expect(
adapter.signAndBroadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
signTxInput,
}),
).rejects.toThrow(/Error signing & broadcasting tx/)
})

it('should return the hash returned by wallet.ethSendTx', async () => {
Expand All @@ -316,11 +324,15 @@ describe('BscChainAdapter', () => {
hash: '0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331',
})

const tx = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>
const signTxInput = { wallet, txToSign: {} } as unknown as SignTxInput<ETHSignTx>

await expect(adapter.signAndBroadcastTransaction(tx)).resolves.toEqual(
'0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331',
)
await expect(
adapter.signAndBroadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
signTxInput,
}),
).resolves.toEqual('0xe670ec64341771606e55d6b4ca35a1a6b75ee3d5145a99d05921026d1527331')
})
})

Expand Down Expand Up @@ -374,7 +386,11 @@ describe('BscChainAdapter', () => {
const adapter = new bsc.ChainAdapter(args)

const mockTx = '0x123'
const result = await adapter.broadcastTransaction(mockTx)
const result = await adapter.broadcastTransaction({
senderAddress: '0x1234',
receiverAddress: '0x1234',
hex: mockTx,
})

expect(args.providers.http.sendTx).toHaveBeenCalledWith<any>({ sendTxBody: { hex: mockTx } })
expect(result).toEqual(expectedResult)
Expand Down
Loading