Skip to content

Commit

Permalink
fix(sdk): Fix asset checks relaychain symbol validation 🛠️
Browse files Browse the repository at this point in the history
  • Loading branch information
michaeldev5 committed Nov 16, 2024
1 parent a164c9b commit 9af800a
Show file tree
Hide file tree
Showing 23 changed files with 254 additions and 269 deletions.
8 changes: 5 additions & 3 deletions apps/playground/src/components/CurrencySelection.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -25,8 +25,10 @@ const CurrencySelection: FC<Props> = ({ form, currencyOptions }) => {
form.setFieldValue("customCurrency", "");
}, [form.values.customCurrencyType]);

const isNotParaToPara =
isRelayChain(form.values.from) || isRelayChain(form.values.to);
const isRelayToPara = isRelayChain(form.values.from);
const isParaToRelay = isRelayChain(form.values.to);

const isNotParaToPara = isRelayToPara || isParaToRelay;

return (
<Stack gap="xs">
Expand Down Expand Up @@ -72,7 +74,7 @@ const CurrencySelection: FC<Props> = ({ form, currencyOptions }) => {
placeholder="Pick value"
data={currencyOptions}
allowDeselect={false}
disabled={isNotParaToPara}
disabled={isRelayToPara}
searchable
required
data-testid="select-currency"
Expand Down
2 changes: 1 addition & 1 deletion apps/playground/src/components/TransferForm.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ const TransferForm: FC<Props> = ({ onSubmit, loading }) => {
};

useEffect(() => {
if (isNotParaToPara) {
if (isNotParaToPara && Object.keys(currencyMap).length === 1) {
form.setFieldValue("currencyOptionId", Object.keys(currencyMap)[0]);
}
}, [isNotParaToPara, currencyMap]);
Expand Down
13 changes: 11 additions & 2 deletions packages/sdk/e2e/xcm-papi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,8 @@ import {
Version,
getOtherAssets,
TNodeDotKsmWithRelayChains,
ForeignAbstract
ForeignAbstract,
determineRelayChain
} from '../src/papi'
import { getPolkadotSigner, PolkadotSigner } from 'polkadot-api/signer'

Expand All @@ -26,6 +27,7 @@ import { keccak_256 } from '@noble/hashes/sha3'
import { mnemonicToSeedSync } from '@scure/bip39'
import { HDKey } from '@scure/bip32'
import { isForeignAsset } from '../src/utils/assets'
import { getAssetBySymbolOrId } from '../src/pallets/assets/getAssetBySymbolOrId'

const MOCK_AMOUNT = 1000
const MOCK_ADDRESS = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'
Expand Down Expand Up @@ -328,9 +330,10 @@ describe.sequential('XCM - e2e', () => {
})
})
filteredNodes.forEach(node => {
const { nodeTo, asset, assetId } = findTransferableNodeAndAsset(node)
if (!nodeTo) return
describe.sequential(`${node} ParaToPara & ParaToRelay`, () => {
let api: TPapiApi
const { nodeTo, asset, assetId } = findTransferableNodeAndAsset(node)
beforeAll(async () => {
api = await createOrGetApiInstanceForNode(node)
})
Expand Down Expand Up @@ -387,6 +390,12 @@ describe.sequential('XCM - e2e', () => {
node !== 'Darwinia' &&
node !== 'Centrifuge'
) {
const relayChainAsset = getAssetBySymbolOrId(
node,
{ symbol: getRelayChainSymbol(node) },
determineRelayChain(node)
)
if (!relayChainAsset) return
it(`should create transfer tx - ParaToRelay ${getRelayChainSymbol(
node
)} from ${node} to Relay`, async () => {
Expand Down
15 changes: 12 additions & 3 deletions packages/sdk/e2e/xcm.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,12 @@ import {
NODE_NAMES_DOT_KSM,
getSupportedAssets,
TNodeDotKsmWithRelayChains,
ForeignAbstract
ForeignAbstract,
determineRelayChain
} from '../src'
import { type ApiPromise } from '@polkadot/api'
import { isForeignAsset } from '../src/utils/assets'
import { getAssetBySymbolOrId } from '../src/pallets/assets/getAssetBySymbolOrId'

const MOCK_AMOUNT = 1000
const MOCK_ADDRESS = '5FHneW46xGXgs5mUiveU4sbTyGBzmstUspZC92UhjJM694ty'
Expand Down Expand Up @@ -240,7 +242,7 @@ describe.sequential('XCM - e2e', () => {
const api = await createApiInstanceForNode('AssetHubPolkadot')
ethAssetSymbols.forEach(symbol => {
if (!symbol) return
it(`should create transfer tx - ${symbol} from Polkadot to Ethereum`, async () => {
it(`should create transfer tx - ${symbol} from AssetHubPolkadot to Ethereum`, async () => {
const tx = await Builder(api)
.from('AssetHubPolkadot')
.to('Ethereum')
Expand Down Expand Up @@ -286,9 +288,10 @@ describe.sequential('XCM - e2e', () => {
})

filteredNodes.forEach(node => {
const { nodeTo, asset, assetId } = findTransferableNodeAndAsset(node)
if (!nodeTo) return
describe.sequential(`${node} ParaToPara & ParaToRelay`, () => {
let api: ApiPromise
const { nodeTo, asset, assetId } = findTransferableNodeAndAsset(node)
beforeAll(async () => {
api = await createApiInstanceForNode(node)
})
Expand Down Expand Up @@ -341,6 +344,12 @@ describe.sequential('XCM - e2e', () => {
node !== 'Centrifuge' &&
node !== 'ParallelHeiko'
) {
const relayChainAsset = getAssetBySymbolOrId(
node,
{ symbol: getRelayChainSymbol(node) },
determineRelayChain(node)
)
if (!relayChainAsset) return
it(`should create transfer tx - ParaToRelay ${getRelayChainSymbol(
node
)} from ${node} to Relay`, async () => {
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk/src/builder/builders/Builder.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -736,6 +736,12 @@ describe('Builder', () => {
})
})

it('should fail to initiate a relay to para transfer to ethereum', () => {
expect(() =>
Builder(mockApi).to('Ethereum').amount(AMOUNT).address(ADDRESS).build()
).toThrow()
})

it('should request a relay to para transfer serialized api call', async () => {
const serializedApiCall = await Builder(mockApi)
.to(NODE_2)
Expand Down
3 changes: 3 additions & 0 deletions packages/sdk/src/builder/builders/Builder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -143,6 +143,9 @@ export class GeneralBuilder<TApi, TRes> {
* @returns An instance of Builder
*/
to(node: TDestination, paraIdTo?: number): AmountBuilder<TApi, TRes> {
if (node === 'Ethereum') {
throw new Error('Transfers from Relay chain to Ethereum are not yet supported.')
}
return RelayToParaBuilder.create(this.api, node, this.batchManager, paraIdTo)
}

Expand Down
8 changes: 4 additions & 4 deletions packages/sdk/src/builder/builders/RelayToParaBuilder.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,10 +4,10 @@ import {
transferRelayToPara,
transferRelayToParaSerializedApiCall
} from '../../pallets/xcmPallet/transfer'
import type { TRelayToParaDestination } from '../../types'
import {
type TSerializedApiCall,
type TRelayToParaOptions,
type TDestination,
type TAddress,
type Version
} from '../../types'
Expand All @@ -30,7 +30,7 @@ class RelayToParaBuilder<TApi, TRes>
UseKeepAliveFinalBuilder<TApi, TRes>
{
private readonly api: IPolkadotApi<TApi, TRes>
private readonly to: TDestination
private readonly to: TRelayToParaDestination
private readonly paraIdTo?: number

private _amount: number
Expand All @@ -40,7 +40,7 @@ class RelayToParaBuilder<TApi, TRes>

private constructor(
api: IPolkadotApi<TApi, TRes>,
to: TDestination,
to: TRelayToParaDestination,
private batchManager: BatchTransactionManager<TApi, TRes>,
paraIdTo?: number
) {
Expand All @@ -52,7 +52,7 @@ class RelayToParaBuilder<TApi, TRes>

static create<TApi, TRes>(
api: IPolkadotApi<TApi, TRes>,
to: TDestination,
to: TRelayToParaDestination,
batchManager: BatchTransactionManager<TApi, TRes>,
paraIdTo?: number
): AmountBuilder<TApi, TRes> {
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk/src/nodes/supported/Hydration.ts
Original file line number Diff line number Diff line change
Expand Up @@ -135,6 +135,8 @@ class Hydration<TApi, TRes>
extends ParachainNode<TApi, TRes>
implements IXTokensTransfer, IPolkadotXCMTransfer
{
private static NATIVE_ASSET_ID = 0

constructor() {
super('Hydration', 'hydradx', 'polkadot', Version.V3)
}
Expand Down Expand Up @@ -261,6 +263,10 @@ class Hydration<TApi, TRes>
transferXTokens<TApi, TRes>(input: XTokensTransferInput<TApi, TRes>) {
const { asset } = input

if (asset.symbol === this.getNativeAssetSymbol()) {
return XTokensTransferImpl.transferXTokens(input, Hydration.NATIVE_ASSET_ID)
}

Check warning on line 268 in packages/sdk/src/nodes/supported/Hydration.ts

View check run for this annotation

Codecov / codecov/patch

packages/sdk/src/nodes/supported/Hydration.ts#L267-L268

Added lines #L267 - L268 were not covered by tests

if (!isForeignAsset(asset)) {
throw new InvalidCurrencyError(`Asset ${JSON.stringify(asset)} has no assetId`)
}
Expand Down
6 changes: 6 additions & 0 deletions packages/sdk/src/nodes/supported/Polkadex.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -38,4 +38,10 @@ describe('Polkadex', () => {

expect(spy).toHaveBeenCalledWith(mockInput, BigInt(123))
})

describe('getProvider', () => {
it('should return Polkadex provider', () => {
expect(polkadex.getProvider()).toBe('wss://polkadex-parachain-rpc.dwellir.com')
})
})
})
12 changes: 4 additions & 8 deletions packages/sdk/src/pallets/assets/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,8 +17,7 @@ import {
type TRelayChainSymbol,
type TNativeAsset
} from '../../types'
import { determineRelayChain, getNode } from '../../utils'
import { getAssetBySymbolOrId } from './getAssetBySymbolOrId'
import { getNode } from '../../utils'

const assetsMap = assetsMapJson as TAssetJsonMap

Expand Down Expand Up @@ -75,10 +74,7 @@ export const getOtherAssets = (node: TNode): TForeignAsset[] => getAssetsObject(
*/
export const getAssets = (node: TNodeWithRelayChains): TAsset[] => {
const { nativeAssets, otherAssets } = getAssetsObject(node)
const relayChainAsset = getAssetBySymbolOrId(determineRelayChain(node), {
symbol: getRelayChainSymbol(node)
})
return [...(relayChainAsset ? [relayChainAsset] : []), ...nativeAssets, ...otherAssets]
return [...nativeAssets, ...otherAssets]

Check warning on line 77 in packages/sdk/src/pallets/assets/assets.ts

View check run for this annotation

Codecov / codecov/patch

packages/sdk/src/pallets/assets/assets.ts#L77

Added line #L77 was not covered by tests
}

/**
Expand All @@ -88,12 +84,12 @@ export const getAssets = (node: TNodeWithRelayChains): TAsset[] => {
* @returns An array of asset symbols.
*/
export const getAllAssetsSymbols = (node: TNodeWithRelayChains): string[] => {
const { relayChainAssetSymbol, nativeAssets, otherAssets } = getAssetsObject(node)
const { nativeAssets, otherAssets } = getAssetsObject(node)
const nativeAssetsSymbols = nativeAssets.map(({ symbol }) => symbol)
const otherAssetsSymbols = otherAssets
.filter(asset => asset.symbol !== undefined)
.map(({ symbol }) => symbol) as string[]
return [relayChainAssetSymbol, ...nativeAssetsSymbols, ...otherAssetsSymbols]
return [...nativeAssetsSymbols, ...otherAssetsSymbols]
}

/**
Expand Down
66 changes: 6 additions & 60 deletions packages/sdk/src/pallets/assets/assetsUtils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,15 +5,12 @@ import type {
TAsset,
TCurrency,
TNativeAsset,
TNodePolkadotKusama,
TNodeWithRelayChains,
TForeignAsset,
TCurrencySymbolValue
} from '../../types'
import { isRelayChain } from '../../utils'
import { isSymbolSpecifier } from '../../utils/assets/isSymbolSpecifier'
import { getDefaultPallet } from '../pallets'
import { getAssetDecimals, getOtherAssets, getRelayChainSymbol } from './assets'
import { getOtherAssets } from './assets'

export const throwDuplicateAssetError = (
symbol: string,
Expand Down Expand Up @@ -62,36 +59,13 @@ export const findBestMatches = (
return matches
}

const findRelayChainSymbol = (node: TNodeWithRelayChains, symbol: TCurrencySymbolValue) => {
const relayChainAssetSymbol = getRelayChainSymbol(node)
if (
(isSymbolSpecifier(symbol) &&
symbol.type === 'Native' &&
relayChainAssetSymbol.toLowerCase() === symbol.value.toLowerCase()) ||
(!isSymbolSpecifier(symbol) && relayChainAssetSymbol.toLowerCase() === symbol.toLowerCase())
) {
const relayChainAsset: TNativeAsset = {
symbol: relayChainAssetSymbol,
decimals: getAssetDecimals(node, relayChainAssetSymbol) ?? undefined
}
return relayChainAsset
}
}

export const findAssetBySymbol = (
node: TNodeWithRelayChains,
destination: TNodeWithRelayChains | undefined,
destination: TNodeWithRelayChains | null,
otherAssets: TForeignAsset[],
nativeAssets: TNativeAsset[],
symbol: TCurrencySymbolValue,
isRelayDestination: boolean,
preferForeignAssets = false
) => {
if (!preferForeignAssets) {
const relayChainAssetMatch = findRelayChainSymbol(node, symbol)
if (relayChainAssetMatch) return relayChainAssetMatch
}

symbol: TCurrencySymbolValue
): TAsset | undefined => {
const supportsESuffix =
node === 'AssetHubPolkadot' ||
node === 'AssetHubKusama' ||
Expand Down Expand Up @@ -199,11 +173,6 @@ export const findAssetBySymbol = (
} else {
const lowerSymbol = symbol.toLowerCase()

const isPolkadotXcm =
!isRelayChain(node) &&
node !== 'Ethereum' &&
getDefaultPallet(node as TNodePolkadotKusama) === 'PolkadotXcm'

let otherAssetsMatches: TForeignAsset[] = []
let nativeAssetsMatches: TNativeAsset[] = []

Expand Down Expand Up @@ -244,9 +213,6 @@ export const findAssetBySymbol = (
nativeAssetsMatches = findBestMatches(nativeAssets, symbol) as TNativeAsset[]

if (nativeAssetsMatches.length > 0 || otherAssetsMatches.length > 0) {
if (isPolkadotXcm) {
return nativeAssetsMatches[0] || otherAssetsMatches[0]
}
return otherAssetsMatches[0] || nativeAssetsMatches[0]
}

Expand All @@ -255,9 +221,6 @@ export const findAssetBySymbol = (
nativeAssetsMatches = findBestMatches(nativeAssets, strippedSymbol) as TNativeAsset[]

if (nativeAssetsMatches.length > 0 || otherAssetsMatches.length > 0) {
if (isPolkadotXcm) {
return nativeAssetsMatches[0] || otherAssetsMatches[0]
}
return otherAssetsMatches[0] || nativeAssetsMatches[0]
}

Expand All @@ -276,10 +239,6 @@ export const findAssetBySymbol = (
otherAssetsMatches = findBestMatches(otherAssets, strippedSymbol) as TForeignAsset[]
nativeAssetsMatches = findBestMatches(nativeAssets, strippedSymbol) as TNativeAsset[]

if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) {
return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined
}

const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length

if (totalMatches > 1) {
Expand All @@ -292,10 +251,6 @@ export const findAssetBySymbol = (
otherAssetsMatches = findBestMatches(otherAssets, prefixedSymbol) as TForeignAsset[]
nativeAssetsMatches = findBestMatches(nativeAssets, prefixedSymbol) as TNativeAsset[]

if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) {
return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined
}

const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length

if (totalMatches > 1) {
Expand All @@ -304,26 +259,17 @@ export const findAssetBySymbol = (
}
}

if (node === 'Astar' || node === 'Shiden' || isPolkadotXcm) {
return nativeAssetsMatches[0] || otherAssetsMatches[0] || undefined
}

const totalMatches = otherAssetsMatches.length + nativeAssetsMatches.length

if (totalMatches > 1 && !isRelayDestination) {
if (totalMatches > 1) {
throwDuplicateAssetError(symbol, nativeAssetsMatches, otherAssetsMatches)
}

return otherAssetsMatches[0] || nativeAssetsMatches[0] || undefined
}
}

if (assetsMatches.length > 0) return assetsMatches[0]

const relayChainAssetMatch = findRelayChainSymbol(node, symbol)
if (relayChainAssetMatch) return relayChainAssetMatch

return undefined
return assetsMatches[0] || undefined
}

export const findAssetById = (assets: TForeignAsset[], assetId: TCurrency) => {
Expand Down
Loading

0 comments on commit 9af800a

Please sign in to comment.