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

Add new verified asset metadata fields to RpcAsset #4878

Merged
merged 6 commits into from
Apr 8, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 4 additions & 4 deletions ironfish-cli/src/utils/asset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -4,18 +4,18 @@

import { Asset } from '@ironfish/rust-nodejs'
import {
AssetVerification,
BufferUtils,
CurrencyUtils,
RpcAsset,
RpcAssetVerification,
RpcClient,
StringUtils,
} from '@ironfish/sdk'
import chalk from 'chalk'
import inquirer from 'inquirer'

type RenderAssetNameOptions = {
verification?: AssetVerification
verification?: RpcAssetVerification
outputType?: string
verbose?: boolean
logWarn?: (msg: string) => void
Expand Down Expand Up @@ -53,9 +53,9 @@ export function renderAssetNameFromHex(

export function compareAssets(
leftName: string,
leftVerification: AssetVerification,
leftVerification: RpcAssetVerification,
rightName: string,
rightVerification: AssetVerification,
rightVerification: RpcAssetVerification,
): number {
const isLeftVerified = leftVerification?.status === 'verified'
const isRightVerified = rightVerification?.status === 'verified'
Expand Down
8 changes: 4 additions & 4 deletions ironfish/src/assets/assetsVerificationApi.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -60,10 +60,10 @@ describe('Assets Verification API Client', () => {
[assetData2.identifier, assetData2],
]),
)
expect(verifiedAssets.isVerified('0123')).toBe(true)
expect(verifiedAssets.isVerified('abcd')).toBe(true)
expect(verifiedAssets.isVerified('4567')).toBe(false)
expect(verifiedAssets.isVerified('89ef')).toBe(false)
expect(verifiedAssets.getAssetData('0123')).toBeDefined()
expect(verifiedAssets.getAssetData('abcd')).toBeDefined()
expect(verifiedAssets.getAssetData('4567')).toBeUndefined()
expect(verifiedAssets.getAssetData('89ef')).toBeUndefined()
})

it('should ignore extra fields in responses', async () => {
Expand Down
19 changes: 11 additions & 8 deletions ironfish/src/assets/assetsVerificationApi.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,17 +5,18 @@ import axios, { AxiosAdapter, AxiosError, AxiosRequestConfig, AxiosResponse } fr
import url, { URL } from 'url'
import { FileSystem } from '../fileSystems'

type AssetData = {
identifier: string
export type VerifiedAssetMetadata = {
symbol: string
decimals?: number
logoURI?: string
website?: string
}

export type VerifiedAssetMetadataResponse = { identifier: string } & VerifiedAssetMetadata

type GetAssetDataResponse = {
version?: number
assets: AssetData[]
assets: VerifiedAssetMetadataResponse[]
}

type GetVerifiedAssetsRequestHeaders = {
Expand All @@ -27,7 +28,7 @@ type GetVerifiedAssetsResponseHeaders = {
}

export class VerifiedAssets {
private readonly assets: Map<string, AssetData> = new Map()
private readonly assets: Map<string, VerifiedAssetMetadataResponse> = new Map()
private lastModified?: string

export(): ExportedVerifiedAssets {
Expand All @@ -46,11 +47,11 @@ export class VerifiedAssets {
return verifiedAssets
}

isVerified(assetId: Buffer | string): boolean {
getAssetData(assetId: Buffer | string): VerifiedAssetMetadata | undefined {
if (!(typeof assetId === 'string')) {
assetId = assetId.toString('hex')
}
return this.assets.has(assetId)
return this.assets.get(assetId)
}
}

Expand All @@ -63,7 +64,7 @@ export class VerifiedAssets {
// - The `assets` field from `VerifiedAssets` is a `Map`, which is not
// properly supported by the cache serializer.
export type ExportedVerifiedAssets = {
assets: AssetData[]
assets: VerifiedAssetMetadataResponse[]
lastModified?: string
}

Expand Down Expand Up @@ -153,7 +154,9 @@ const axiosFileAdapter =
}))
}

const sanitizedAssetData = (assetData: AssetData): AssetData => {
const sanitizedAssetData = (
assetData: VerifiedAssetMetadataResponse,
): VerifiedAssetMetadataResponse => {
return {
identifier: assetData.identifier,
symbol: assetData.symbol,
Expand Down
96 changes: 84 additions & 12 deletions ironfish/src/assets/assetsVerifier.test.ts
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,13 @@ describe('AssetsVerifier', () => {
expect(refresh).toHaveBeenCalledTimes(1)
await waitForRefreshToFinish(refresh)

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })
expect(assetsVerifier.verify('89ab')).toStrictEqual({ status: 'unverified' })

Expand All @@ -102,7 +108,13 @@ describe('AssetsVerifier', () => {
await waitForRefreshToFinish(refresh)

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'unverified' })
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('4567')).toStrictEqual({
status: 'verified',
symbol: assetData2.symbol,
decimals: assetData2.decimals,
logoURI: assetData2.logoURI,
website: assetData2.website,
})
expect(assetsVerifier.verify('89ab')).toStrictEqual({ status: 'unverified' })

jest.runOnlyPendingTimers()
Expand All @@ -111,7 +123,13 @@ describe('AssetsVerifier', () => {

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'unverified' })
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })
expect(assetsVerifier.verify('89ab')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('89ab')).toStrictEqual({
status: 'verified',
symbol: assetData3.symbol,
decimals: assetData3.decimals,
logoURI: assetData3.logoURI,
website: assetData3.website,
})
})

it('does not do any refresh after being stopped', async () => {
Expand Down Expand Up @@ -214,7 +232,13 @@ describe('AssetsVerifier', () => {
assetsVerifier.start()
await waitForRefreshToFinish(refresh)

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
})

it("returns 'unverified' when the API does not list the asset", async () => {
Expand Down Expand Up @@ -256,7 +280,13 @@ describe('AssetsVerifier', () => {
await waitForRefreshToFinish(refresh)

expect(warn).not.toHaveBeenCalled()
expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })

jest.runOnlyPendingTimers()
Expand All @@ -265,7 +295,13 @@ describe('AssetsVerifier', () => {
expect(warn).toHaveBeenCalledWith(
'Error while fetching verified assets: Request failed with status code 500',
)
expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })
})

Expand All @@ -285,12 +321,24 @@ describe('AssetsVerifier', () => {
assetsVerifier.start()
await waitForRefreshToFinish(refresh)

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })

assetsVerifier.stop()

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })
})
})
Expand Down Expand Up @@ -322,7 +370,13 @@ describe('AssetsVerifier', () => {

assetsVerifier.start()

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })

await waitForRefreshToFinish(refresh)
Expand Down Expand Up @@ -360,7 +414,13 @@ describe('AssetsVerifier', () => {
await waitForRefreshToFinish(refresh)

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'unverified' })
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('4567')).toStrictEqual({
status: 'verified',
symbol: assetData2.symbol,
decimals: assetData2.decimals,
logoURI: assetData2.logoURI,
website: assetData2.website,
})
})

it('saves the persistent cache after every update', async () => {
Expand Down Expand Up @@ -436,12 +496,24 @@ describe('AssetsVerifier', () => {

assetsVerifier.start()

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })

await waitForRefreshToFinish(refresh)

expect(assetsVerifier.verify('0123')).toStrictEqual({ status: 'verified' })
expect(assetsVerifier.verify('0123')).toStrictEqual({
status: 'verified',
symbol: assetData1.symbol,
decimals: assetData1.decimals,
logoURI: assetData1.logoURI,
website: assetData1.website,
})
expect(assetsVerifier.verify('4567')).toStrictEqual({ status: 'unverified' })
expect(setManySpy).not.toHaveBeenCalled()
expect(saveSpy).not.toHaveBeenCalled()
Expand Down
27 changes: 21 additions & 6 deletions ironfish/src/assets/assetsVerifier.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,15 @@ import { createRootLogger, Logger } from '../logger'
import { ErrorUtils } from '../utils'
import { SetIntervalToken } from '../utils'
import { Retry } from '../utils'
import { AssetsVerificationApi, VerifiedAssets } from './assetsVerificationApi'
import {
AssetsVerificationApi,
VerifiedAssetMetadata,
VerifiedAssets,
} from './assetsVerificationApi'

export type AssetVerification = {
status: 'verified' | 'unverified' | 'unknown'
}
export type AssetVerification =
| { status: 'unverified' | 'unknown' }
| ({ status: 'verified' } & VerifiedAssetMetadata)

export class AssetsVerifier {
private readonly REFRESH_INTERVAL = 6 * 60 * 60 * 1000 // 6 hours
Expand Down Expand Up @@ -103,13 +107,24 @@ export class AssetsVerifier {
return this.cache.save()
}

getAssetData(assetId: Buffer | string): VerifiedAssetMetadata | undefined {
return this.verifiedAssets?.getAssetData(assetId)
}

verify(assetId: Buffer | string): AssetVerification {
if (!this.verifiedAssets) {
return { status: 'unknown' }
}

if (this.verifiedAssets.isVerified(assetId)) {
return { status: 'verified' }
const assetData = this.getAssetData(assetId)
if (assetData) {
return {
status: 'verified',
symbol: assetData.symbol,
decimals: assetData.decimals,
logoURI: assetData.logoURI,
website: assetData.website,
}
} else {
return { status: 'unverified' }
}
Expand Down
16 changes: 13 additions & 3 deletions ironfish/src/rpc/routes/types.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,12 +3,16 @@
* file, You can obtain one at https://mozilla.org/MPL/2.0/. */

import * as yup from 'yup'
import { AssetVerification } from '../../assets'
import { AssetVerification, VerifiedAssetMetadata } from '../../assets'
import { Connection } from '../../network'
import { Features } from '../../network/peers/peerFeatures'
import { BlockHeader } from '../../primitives'
import { RpcTransaction, RpcTransactionSchema } from './chain/types'

export type RpcAssetVerification = {
status: AssetVerification['status']
} & Partial<VerifiedAssetMetadata>

export type RpcBurn = {
assetId: string
value: string
Expand Down Expand Up @@ -78,7 +82,7 @@ export type RpcAsset = {
creator: string
metadata: string
createdTransactionHash: string
verification: AssetVerification
verification: RpcAssetVerification
supply?: string
/**
* @deprecated query for the transaction to find it's status
Expand All @@ -94,7 +98,13 @@ export const RpcAssetSchema: yup.ObjectSchema<RpcAsset> = yup
nonce: yup.number().required(),
creator: yup.string().required(),
verification: yup
.object({ status: yup.string().oneOf(['verified', 'unverified', 'unknown']).defined() })
.object({
status: yup.string().oneOf(['verified', 'unverified', 'unknown']).defined(),
symbol: yup.string().optional(),
decimals: yup.number().optional(),
logoURI: yup.string().optional(),
website: yup.string().optional(),
})
.defined(),
status: yup.string().defined(),
supply: yup.string().optional(),
Expand Down
2 changes: 1 addition & 1 deletion ironfish/src/rpc/routes/wallet/burnAsset.ts
Original file line number Diff line number Diff line change
Expand Up @@ -105,11 +105,11 @@ routes.register<typeof BurnAssetRequestSchema, BurnAssetResponse>(
nonce: asset.nonce,
creator: asset.creator.toString('hex'),
owner: asset.owner.toString('hex'),
verification: context.assetsVerifier.verify(asset.id),
status: await context.wallet.getAssetStatus(account, asset, {
confirmations: request.data.confirmations,
}),
createdTransactionHash: asset.createdTransactionHash.toString('hex'),
verification: context.assetsVerifier.verify(asset.id),
},
transaction: await serializeRpcWalletTransaction(
context.config,
Expand Down
Loading
Loading