Skip to content

Commit

Permalink
Update regex parsing + unbonding case
Browse files Browse the repository at this point in the history
  • Loading branch information
grod220 committed Feb 22, 2024
1 parent 16bf2c6 commit 5551ec5
Show file tree
Hide file tree
Showing 6 changed files with 101 additions and 58 deletions.
35 changes: 25 additions & 10 deletions packages/constants/assets/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -3,19 +3,34 @@ import LocalAssetRegistry from './local-asset-registry.json';
import { JsonValue } from '@bufbuild/protobuf';

export interface AssetPattens {
lpNftPattern: RegExp;
delegationTokenPattern: RegExp;
proposalNftPattern: RegExp;
unbondingTokenPattern: RegExp;
votingReceiptPattern: RegExp;
lpNft: RegExp;
delegationToken: RegExp;
proposalNft: RegExp;
unbondingToken: RegExp;
votingReceipt: RegExp;
}

export interface DelegationCaptureGroups {
id: string;
bech32IdentityKey: string;
}

export interface UnbondingCaptureGroups {
epoch: string;
id: string;
}

// Source of truth for regex patterns: https://github.com/penumbra-zone/penumbra/blob/main/crates/core/asset/src/asset/registry.rs
export const assetPatterns: AssetPattens = {
lpNftPattern: new RegExp('^lpnft_'),
delegationTokenPattern: new RegExp('^delegation_'),
proposalNftPattern: new RegExp('^proposal_'),
unbondingTokenPattern: new RegExp('^unbonding_'),
votingReceiptPattern: new RegExp('^voted_on_'),
lpNft: new RegExp(/^lpnft_/),
delegationToken: new RegExp(
/.*delegation_(?<bech32IdentityKey>penumbravalid1(?<id>[a-zA-HJ-NP-Z0-9]+))$/,
),
proposalNft: new RegExp(/^proposal_/),
unbondingToken: new RegExp(
/.*unbonding_epoch_(?<epoch>[0-9]+)_penumbravalid1(?<id>[a-zA-HJ-NP-Z0-9]+)$/,
),
votingReceipt: new RegExp(/^voted_on_/),
};

export const localAssets: Metadata[] = LocalAssetRegistry.map(a =>
Expand Down
45 changes: 7 additions & 38 deletions packages/query/src/block-processor.ts
Original file line number Diff line number Diff line change
@@ -1,17 +1,12 @@
import { RootQuerier } from './root-querier';

import { bech32 } from 'bech32';

import { sha256Hash } from '@penumbra-zone/crypto-web';
import {
BlockProcessorInterface,
IndexedDbInterface,
ViewServerInterface,
} from '@penumbra-zone/types';
import { assetPatterns } from '@penumbra-zone/constants';
import { computePositionId, decodeSctRoot, transactionInfo } from '@penumbra-zone/wasm';

import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
import {
PositionState,
PositionState_PositionStateEnum,
Expand All @@ -29,6 +24,7 @@ import {
TransactionInfo,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/view/v1/view_pb';
import { backOff } from 'exponential-backoff';
import { customizeSymbol } from './customize-symbol';

interface QueryClientProps {
fullViewingKey: string;
Expand Down Expand Up @@ -247,43 +243,16 @@ export class BlockProcessor implements BlockProcessorInterface {
for (const n of newNotes) {
const assetId = n.note?.value?.assetId;
if (!assetId) continue;
if (await this.indexedDb.getAssetsMetadata(assetId)) continue;

let metadata: Metadata | undefined;
metadata = await this.querier.shieldedPool.assetMetadata(assetId);

// If the metadata is for a delegation token, customize its symbol.
if (metadata && assetPatterns.delegationTokenPattern.test(metadata.display)) {
// We can't trust the validator's self-described name, so use their validator ID.
// We know it's delegation_penumbravalid1... so use substrings:

// TODO: what's the best way to handle delegation tokens to unknown validators?
const metadataInDb = await this.indexedDb.getAssetsMetadata(assetId);
if (metadataInDb) continue;

// Find the index of '1' in the string
const index = metadata.display.indexOf('1');
// Get the first N characters after the '1'
const id = metadata.display.substring(index + 1, index + 1 + 24);
const metadataFromNode = await this.querier.shieldedPool.assetMetadata(assetId);

metadata.symbol = 'Delegated UM (' + id + '...)';
if (metadataFromNode) {
customizeSymbol(metadataFromNode);
await this.indexedDb.saveAssetsMetadata(metadataFromNode);
}

// TODO: unbonding tokens?

// Note: the below code is incorrect, the asset ID is the hash of the denom,
// so this is actually generating metadata for a different asset. Not sure
// when/if this is used.
if (!metadata) {
const UNNAMED_ASSET_PREFIX = 'passet';
const denom = bech32.encode(UNNAMED_ASSET_PREFIX, bech32.toWords(assetId.inner));
metadata = new Metadata({
base: denom,
denomUnits: [{ aliases: [], denom, exponent: 0 }],
display: denom,
penumbraAssetId: assetId,
});
}

await this.indexedDb.saveAssetsMetadata(metadata);
}
}

Expand Down
32 changes: 32 additions & 0 deletions packages/query/src/customize-symbol.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
import { describe, expect, test } from 'vitest';
import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
import { customizeSymbol } from './customize-symbol';

describe('Customizing metadata', () => {
test('should work for delegation token', () => {
const metadata = new Metadata({
display:
'delegation_penumbravalid1fjuj67ayaqueqxg03d65ps5aah6m39u39qeacu3zv2cw3dzxssyq3yrcez',
});
customizeSymbol(metadata);
expect(metadata.symbol).toBe('Delegated UM (fjuj67ayaqueqxg03d65ps5aa...)');
});

test('should work for unbonding token', () => {
const metadata = new Metadata({
display:
'uunbonding_epoch_29_penumbravalid1fjuj67ayaqueqxg03d65ps5aah6m39u39qeacu3zv2cw3dzxssyq3yrcez',
});
customizeSymbol(metadata);
expect(metadata.symbol).toBe('Unbonding UM, epoch 29 (fjuj67ayaqueqxg03d65ps5aa...)');
});

test('should do nothing if no matches', () => {
const metadata = new Metadata({
display: 'test_usd',
symbol: 'usdc',
});
customizeSymbol(metadata);
expect(metadata.symbol).toBe('usdc');
});
});
27 changes: 27 additions & 0 deletions packages/query/src/customize-symbol.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb';
import {
assetPatterns,
DelegationCaptureGroups,
UnbondingCaptureGroups,
} from '@penumbra-zone/constants';

const DELEGATION_SYMBOL_LENGTH = 50 - 'delegation_penumbravalid1'.length;
const UNBONDING_SYMBOL_LENGTH = 41 - 'unbonding_epoch_'.length;

// If the metadata is for a delegation or unbonding tokens, customize its symbol.
// We can't trust the validator's self-described name, so use their validator ID (in metadata.display).
export const customizeSymbol = (metadata: Metadata) => {
const delegationMatch = assetPatterns.delegationToken.exec(metadata.display);
if (delegationMatch) {
const { id } = delegationMatch.groups as unknown as DelegationCaptureGroups;
const shortenedId = id.slice(0, DELEGATION_SYMBOL_LENGTH);
metadata.symbol = `Delegated UM (${shortenedId}...)`;
}

const unbondingMatch = assetPatterns.unbondingToken.exec(metadata.display);
if (unbondingMatch) {
const { id, epoch } = unbondingMatch.groups as unknown as UnbondingCaptureGroups;
const shortenedId = id.slice(0, UNBONDING_SYMBOL_LENGTH);
metadata.symbol = `Unbonding UM, epoch ${epoch} (${shortenedId}...)`;
}
};
10 changes: 5 additions & 5 deletions packages/router/src/grpc/view-protocol-server/assets.ts
Original file line number Diff line number Diff line change
Expand Up @@ -22,23 +22,23 @@ export const assets: Impl['assets'] = async function* (req, ctx) {
}[] = [
{
includeReq: includeLpNfts,
pattern: assetPatterns.lpNftPattern,
pattern: assetPatterns.lpNft,
},
{
includeReq: includeDelegationTokens,
pattern: assetPatterns.delegationTokenPattern,
pattern: assetPatterns.delegationToken,
},
{
includeReq: includeProposalNfts,
pattern: assetPatterns.proposalNftPattern,
pattern: assetPatterns.proposalNft,
},
{
includeReq: includeUnbondingTokens,
pattern: assetPatterns.unbondingTokenPattern,
pattern: assetPatterns.unbondingToken,
},
{
includeReq: includeVotingReceiptTokens,
pattern: assetPatterns.votingReceiptPattern,
pattern: assetPatterns.votingReceipt,
},
...includeSpecificDenominations.map(d => ({
includeReq: true,
Expand Down
10 changes: 5 additions & 5 deletions packages/storage/src/indexed-db/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ import {
AddressIndex,
IdentityKey,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb';
import { assetPatterns, localAssets } from '@penumbra-zone/constants';
import { assetPatterns, DelegationCaptureGroups, localAssets } from '@penumbra-zone/constants';
import {
Position,
PositionId,
Expand Down Expand Up @@ -319,7 +319,7 @@ export class IndexedDb implements IndexedDbInterface {
for await (const assetCursor of this.db.transaction('ASSETS').store) {
const denomMetadata = Metadata.fromJson(assetCursor.value);
if (
assetPatterns.delegationTokenPattern.test(denomMetadata.display) &&
assetPatterns.delegationToken.test(denomMetadata.display) &&
denomMetadata.penumbraAssetId
) {
delegationAssets.set(uint8ArrayToHex(denomMetadata.penumbraAssetId.inner), denomMetadata);
Expand Down Expand Up @@ -354,10 +354,10 @@ export class IndexedDb implements IndexedDbInterface {
// delegation asset denom consists of prefix 'delegation_' and validator identity key in bech32m encoding
// For example, in denom 'delegation_penumbravalid12s9lanucncnyasrsqgy6z532q7nwsw3aqzzeqqas55kkpyf6lhsqs2w0zar'
// 'penumbravalid12s9lanucncnyasrsqgy6z532q7nwsw3aqzzeqas55kkpyf6lhsqs2w0zar' is validator identity key.
const bech32IdentityKey = asset?.display.replace(assetPatterns.delegationTokenPattern, '');
const regexResult = assetPatterns.delegationToken.exec(asset?.display ?? '');
if (!regexResult) throw new Error('expected delegation token identity key not present');

if (!bech32IdentityKey)
throw new Error('expected delegation token identity key not present');
const { bech32IdentityKey } = regexResult.groups as unknown as DelegationCaptureGroups;

notesForVoting.push(
new NotesForVotingResponse({
Expand Down

0 comments on commit 5551ec5

Please sign in to comment.