Skip to content

Commit

Permalink
fix: hotfix for not allowing send to ANY asset address (#1657)
Browse files Browse the repository at this point in the history
  • Loading branch information
LuizAsFight authored Nov 18, 2024
1 parent 4c7f9ed commit 2e3fa77
Show file tree
Hide file tree
Showing 10 changed files with 1,328 additions and 231 deletions.
5 changes: 5 additions & 0 deletions .changeset/weak-schools-drum.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
---
"fuels-wallet": patch
---

fix: hotfix for not allowing send to ANY asset address
3 changes: 2 additions & 1 deletion package.json
Original file line number Diff line number Diff line change
Expand Up @@ -125,7 +125,8 @@
"rollup@>=4.0.0 <4.22.4": ">=4.22.4",
"fuels": "0.96.1",
"secp256k1@=5.0.0": ">=5.0.1",
"elliptic@<6.6.0": ">=6.6.0"
"elliptic@<6.6.0": ">=6.6.0",
"cross-spawn@<7.0.5": ">=7.0.5"
}
}
}
1 change: 1 addition & 0 deletions packages/app/playwright/crx/utils/popup.ts
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ export async function switchAccount(popupPage: Page, name: string) {
return account;
}

await popupPage.waitForTimeout(2000);
await getByAriaLabel(popupPage, 'Accounts').click();

await hasText(popupPage, name);
Expand Down
10 changes: 10 additions & 0 deletions packages/app/playwright/e2e/SendTransaction.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -356,4 +356,14 @@ test.describe('SendTransaction', () => {
// Wait for transaction to be confirmed
await hasText(page, 'success');
});

test('Send transaction to an asset address should fail', async () => {
const assetAddress = provider.getBaseAssetId();
await visit(page, '/send');
await getInputByName(page, 'address').fill(assetAddress);
await getInputByName(page, 'amount').fill('0.001');
await expect(
page.getByText("You can't send to Asset address")
).toBeVisible();
});
});
40 changes: 25 additions & 15 deletions packages/app/src/systems/Asset/cache/AssetsCache.ts
Original file line number Diff line number Diff line change
@@ -1,19 +1,22 @@
import type { AssetData } from '@fuel-wallet/types';
import type { Asset, AssetFuel, Provider } from 'fuels';
import type { AssetFuel } from 'fuels';
import { AssetService } from '~/systems/Asset/services';
import { getFuelAssetByAssetId } from '~/systems/Asset/utils';
import { db } from '~/systems/Core/utils/database';
import { type FuelCachedAsset, db } from '~/systems/Core/utils/database';

type Endpoint = {
chainId: number;
url: string;
};

const FIVE_MINUTES = 5 * 60 * 1000;
export const assetDbKeyFactory = (chainId: number, assetId: string) =>
`${chainId}/asset/${assetId}`;

export class AssetsCache {
private cache: {
[chainId: number]: {
[assetId: string]: Asset & { fetchedAt?: number };
[assetId: string]: FuelCachedAsset;
};
};
private dbAssetsCache: {
Expand Down Expand Up @@ -60,7 +63,7 @@ export class AssetsCache {
instance
.getAsset({ chainId, assetId, dbAssets })
.then((asset) => {
assetData.set(assetId, asset);
asset && assetData.set(assetId, asset);
})
.catch((e) => {
console.error('Error fetching asset from indexer', e);
Expand Down Expand Up @@ -89,9 +92,16 @@ export class AssetsCache {
} catch (_e: unknown) {}
}

assetIsValid(asset: AssetData) {
assetIsValid(asset: FuelCachedAsset) {
const isNftAsset = asset.isNft && !asset.decimals;
// Non-NFT assets (not account addresses)
const isNonNftAsset = !asset.isNft && !!asset.decimals;
const isZeroDecimalAsset = !asset.isNft && !asset.decimals && asset.symbol;
return (
asset.name != null && 'fetchedAt' in asset && asset.fetchedAt != null
asset.name != null &&
'fetchedAt' in asset &&
asset.fetchedAt != null &&
(isNftAsset || isNonNftAsset || isZeroDecimalAsset)
);
}

Expand All @@ -105,7 +115,7 @@ export class AssetsCache {
assetId: string;
dbAssets: AssetData[];
save?: boolean;
}) {
}): Promise<FuelCachedAsset | undefined> {
if (chainId == null || !assetId) {
return;
}
Expand All @@ -129,14 +139,16 @@ export class AssetsCache {
}

// get from indexed db if not in memory
const assetFromDb = await this.storage.getItem(`${chainId}/${assetId}`);
const assetFromDb = await this.storage.getItem(
assetDbKeyFactory(chainId, assetId)
);
if (
assetFromDb?.name &&
assetFromDb.fetchedAt &&
now - assetFromDb.fetchedAt < FIVE_MINUTES
) {
this.cache[chainId][assetId] = assetFromDb;
return assetFromDb;
return assetFromDb as FuelCachedAsset;
}

const dbAsset = await getFuelAssetByAssetId({
Expand All @@ -155,16 +167,14 @@ export class AssetsCache {
return undefined;
});

console.log('asd assetFromIndexer', assetFromIndexer);

const {
isNFT,
metadata,
name: indexerAssetName,
symbol: indexerAssetSymbol,
...rest
} = assetFromIndexer ?? {};
const asset = {
const asset: FuelCachedAsset = {
...dbAsset,
isNft: !!isNFT,
...rest,
Expand All @@ -183,7 +193,7 @@ export class AssetsCache {

if (save) {
this.cache[chainId][assetId] = asset;
this.storage.setItem(`${chainId}/${assetId}`, asset);
this.storage.setItem(assetDbKeyFactory(chainId, assetId), asset);
}
return asset;
}
Expand All @@ -204,9 +214,9 @@ class IndexedAssetsDB {
});
}

async setItem(key: string, data: AssetData) {
async setItem(key: string, data: FuelCachedAsset) {
await db.transaction('rw', db.indexedAssets, async () => {
await db.indexedAssets.put({ key, ...data });
await db.indexedAssets.put({ ...data, key });
});
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,6 +37,7 @@ export type AssetItemProps = {
onRemove?: (assetId: string) => void;
onEdit?: (assetId: string) => void;
shouldShowAddAssetBtn?: boolean;
shouldShowCopyAssetAddress?: boolean;
};

type AssetItemComponent = FC<AssetItemProps> & {
Expand All @@ -51,6 +52,7 @@ export const AssetItem: AssetItemComponent = ({
onRemove,
onEdit,
shouldShowAddAssetBtn,
shouldShowCopyAssetAddress,
}) => {
const navigate = useNavigate();
const { visibility } = useBalanceVisibility();
Expand All @@ -75,7 +77,7 @@ export const AssetItem: AssetItemComponent = ({
const { assetId, name, symbol, icon, decimals, isCustom } = asset;

function getLeftEl() {
if (assetId) {
if (assetId && shouldShowCopyAssetAddress) {
return (
<Copyable
value={assetId}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,7 @@ export const AssetList: AssetListComponent = ({
showActions={showActions}
onRemove={onRemove}
onEdit={onEdit}
shouldShowCopyAssetAddress={true}
/>
))}
{!!(!isLoading && unknownLength) && (
Expand Down
8 changes: 4 additions & 4 deletions packages/app/src/systems/Core/utils/database.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,10 +10,13 @@ import type {
} from '@fuel-wallet/types';
import Dexie, { type DbEvents, type PromiseExtended, type Table } from 'dexie';
import 'dexie-observable';
import type { AssetFuel } from 'fuels';
import type { TransactionCursor } from '~/systems/Transaction';
import { applyDbVersioning } from './databaseVersioning';

type FailureEvents = Extract<keyof DbEvents, 'close' | 'blocked'>;
export type FuelCachedAsset = AssetData &
AssetFuel & { key: string; fetchedAt?: number };

export class FuelDB extends Dexie {
vaults!: Table<Vault, string>;
Expand All @@ -22,10 +25,7 @@ export class FuelDB extends Dexie {
connections!: Table<Connection, string>;
transactionsCursors!: Table<TransactionCursor, string>;
assets!: Table<AssetData, string>;
indexedAssets!: Table<
AssetData & { key: string; fetchedAt?: number },
string
>;
indexedAssets!: Table<FuelCachedAsset, string>;
abis!: Table<AbiTable, string>;
errors!: Table<StoredFuelWalletError, string>;
integrityCheckInterval?: NodeJS.Timeout;
Expand Down
4 changes: 2 additions & 2 deletions packages/app/src/systems/Send/hooks/useSend.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import { yupResolver } from '@hookform/resolvers/yup';
import { useInterpret, useSelector } from '@xstate/react';
import type { BN, BNInput } from 'fuels';
import { Address, type Provider, bn, isB256 } from 'fuels';
import { useCallback, useEffect, useMemo, useState } from 'react';
import { useCallback, useEffect, useMemo } from 'react';
import { useForm, useWatch } from 'react-hook-form';
import { useNavigate } from 'react-router-dom';
import * as yup from 'yup';
Expand Down Expand Up @@ -149,7 +149,7 @@ const schemaFactory = (provider?: Provider) =>

if (
assetCached &&
!AssetsCache.getInstance().assetIsValid(assetCached)
AssetsCache.getInstance().assetIsValid(assetCached)
) {
return ctx.createError({
message: `You can't send to Asset address`,
Expand Down
Loading

0 comments on commit 2e3fa77

Please sign in to comment.