diff --git a/packages/query/src/block-processor.ts b/packages/query/src/block-processor.ts index c90f66d92f..a481b2d80b 100644 --- a/packages/query/src/block-processor.ts +++ b/packages/query/src/block-processor.ts @@ -14,7 +14,10 @@ import { CommitmentSource, Nullifier, } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/sct/v1/sct_pb'; -import { Transaction } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; +import { + Action, + Transaction, +} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/transaction/v1/transaction_pb'; import { TransactionId } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/txhash/v1/txhash_pb'; import { StateCommitment } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/crypto/tct/v1/tct_pb'; import { @@ -37,6 +40,7 @@ import { PRICE_RELEVANCE_THRESHOLDS } from '@penumbra-zone/constants/assets'; import { toDecimalExchangeRate } from '@penumbra-zone/types/amount'; import { ValidatorInfoResponse } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/stake/v1/stake_pb'; import { uint8ArrayToHex } from '@penumbra-zone/types/hex'; +import { getAuctionId, getAuctionNftMetadata } from '@penumbra-zone/wasm/auction'; declare global { // `var` required for global declaration (as let/const are block scoped) @@ -52,10 +56,16 @@ interface QueryClientProps { stakingTokenMetadata: Metadata; } -const blankTxSource = new CommitmentSource({ +const BLANK_TX_SOURCE = new CommitmentSource({ source: { case: 'transaction', value: { id: new Uint8Array() } }, }); +const POSITION_STATES: PositionState[] = [ + new PositionState({ state: PositionState_PositionStateEnum.OPENED }), + new PositionState({ state: PositionState_PositionStateEnum.CLOSED }), + new PositionState({ state: PositionState_PositionStateEnum.WITHDRAWN, sequence: 0n }), +]; + export class BlockProcessor implements BlockProcessorInterface { private readonly querier: RootQuerier; private readonly indexedDb: IndexedDbInterface; @@ -142,7 +152,7 @@ export class BlockProcessor implements BlockProcessorInterface { if (txCommitments.some(txCommitment => stateCommitment.equals(txCommitment))) { txId ??= new TransactionId({ inner: await sha256Hash(tx.toBinary()) }); relevantTx.set(txId, tx); - if (blankTxSource.equals(spendableNoteRecord.source)) { + if (BLANK_TX_SOURCE.equals(spendableNoteRecord.source)) { spendableNoteRecord.source = new CommitmentSource({ source: { case: 'transaction', value: { id: txId.inner } }, }); @@ -261,7 +271,7 @@ export class BlockProcessor implements BlockProcessorInterface { // - detect LpNft position opens // - generate all possible position state metadata // - update idb - await this.identifyLpNftPositions(blockTx); + await this.processTransactions(blockTx); // at this point txinfo can be generated and saved. this will resolve // pending broadcasts, and populate the transaction list. @@ -361,45 +371,66 @@ export class BlockProcessor implements BlockProcessorInterface { return spentNullifiers; } - private async identifyLpNftPositions(txs: Transaction[]) { - const positionStates: PositionState[] = [ - new PositionState({ state: PositionState_PositionStateEnum.OPENED }), - new PositionState({ state: PositionState_PositionStateEnum.CLOSED }), - new PositionState({ state: PositionState_PositionStateEnum.WITHDRAWN, sequence: 0n }), - ]; - + private async processTransactions(txs: Transaction[]) { for (const tx of txs) { for (const { action } of tx.body?.actions ?? []) { - if (action.case === 'positionOpen' && action.value.position) { - for (const state of positionStates) { - const metadata = getLpNftMetadata(computePositionId(action.value.position), state); - await this.indexedDb.saveAssetsMetadata(metadata); - } - // to optimize on-chain storage PositionId is not written in the positionOpen action, - // but can be computed via hashing of immutable position fields - await this.indexedDb.addPosition( - computePositionId(action.value.position), - action.value.position, - ); - } - if (action.case === 'positionClose' && action.value.positionId) { - await this.indexedDb.updatePosition( - action.value.positionId, - new PositionState({ state: PositionState_PositionStateEnum.CLOSED }), - ); - } - if (action.case === 'positionWithdraw' && action.value.positionId) { - // Record the LPNFT for the current sequence number. - const positionState = new PositionState({ - state: PositionState_PositionStateEnum.WITHDRAWN, - sequence: action.value.sequence, - }); - const metadata = getLpNftMetadata(action.value.positionId, positionState); - await this.indexedDb.saveAssetsMetadata(metadata); - - await this.indexedDb.updatePosition(action.value.positionId, positionState); - } + await Promise.all([this.identifyAuctionNfts(action), this.identifyLpNftPositions(action)]); + } + } + } + + private async identifyAuctionNfts(action: Action['action']) { + if (action.case === 'actionDutchAuctionSchedule' && action.value.description) { + const auctionId = getAuctionId(action.value.description); + const metadata = getAuctionNftMetadata( + auctionId, + // Always a sequence number of 0 when starting a Dutch auction + 0n, + ); + await this.indexedDb.saveAssetsMetadata(metadata); + } else if (action.case === 'actionDutchAuctionEnd' && action.value.auctionId) { + const metadata = getAuctionNftMetadata( + action.value.auctionId, + // Always a sequence number of 1 when ending a Dutch auction + 1n, + ); + await this.indexedDb.saveAssetsMetadata(metadata); + } + /** + * @todo Handle `actionDutchAuctionWithdraw`, and figure out how to + * determine the sequence number if there have been multiple withdrawals. + */ + } + + private async identifyLpNftPositions(action: Action['action']) { + if (action.case === 'positionOpen' && action.value.position) { + for (const state of POSITION_STATES) { + const metadata = getLpNftMetadata(computePositionId(action.value.position), state); + await this.indexedDb.saveAssetsMetadata(metadata); } + // to optimize on-chain storage PositionId is not written in the positionOpen action, + // but can be computed via hashing of immutable position fields + await this.indexedDb.addPosition( + computePositionId(action.value.position), + action.value.position, + ); + } + if (action.case === 'positionClose' && action.value.positionId) { + await this.indexedDb.updatePosition( + action.value.positionId, + new PositionState({ state: PositionState_PositionStateEnum.CLOSED }), + ); + } + if (action.case === 'positionWithdraw' && action.value.positionId) { + // Record the LPNFT for the current sequence number. + const positionState = new PositionState({ + state: PositionState_PositionStateEnum.WITHDRAWN, + sequence: action.value.sequence, + }); + const metadata = getLpNftMetadata(action.value.positionId, positionState); + await this.indexedDb.saveAssetsMetadata(metadata); + + await this.indexedDb.updatePosition(action.value.positionId, positionState); } } diff --git a/packages/wasm/crate/Cargo.lock b/packages/wasm/crate/Cargo.lock index d989abdff3..dcfb71c940 100644 --- a/packages/wasm/crate/Cargo.lock +++ b/packages/wasm/crate/Cargo.lock @@ -776,8 +776,8 @@ dependencies = [ [[package]] name = "decaf377-fmd" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "ark-ff", "ark-serialize", @@ -790,8 +790,8 @@ dependencies = [ [[package]] name = "decaf377-ka" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "ark-ff", "decaf377 0.5.0", @@ -2087,8 +2087,8 @@ dependencies = [ [[package]] name = "penumbra-asset" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2123,10 +2123,56 @@ dependencies = [ "tracing", ] +[[package]] +name = "penumbra-auction" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" +dependencies = [ + "anyhow", + "ark-ff", + "ark-groth16", + "ark-r1cs-std", + "ark-relations", + "ark-serialize", + "ark-snark", + "base64 0.21.7", + "bech32", + "bitvec", + "blake2b_simd 1.0.2", + "decaf377 0.5.0", + "decaf377-rdsa", + "hex", + "metrics", + "once_cell", + "pbjson-types", + "penumbra-asset", + "penumbra-dex", + "penumbra-keys", + "penumbra-num", + "penumbra-proof-params", + "penumbra-proto", + "penumbra-sct", + "penumbra-shielded-pool", + "penumbra-tct", + "penumbra-txhash", + "prost", + "prost-types", + "rand_chacha", + "rand_core", + "regex", + "serde", + "serde_unit_struct", + "serde_with", + "sha2 0.10.8", + "tap", + "tendermint", + "tracing", +] + [[package]] name = "penumbra-community-pool" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2155,8 +2201,8 @@ dependencies = [ [[package]] name = "penumbra-compact-block" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2187,8 +2233,8 @@ dependencies = [ [[package]] name = "penumbra-dex" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2239,8 +2285,8 @@ dependencies = [ [[package]] name = "penumbra-distributions" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "async-trait", @@ -2255,8 +2301,8 @@ dependencies = [ [[package]] name = "penumbra-fee" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2278,8 +2324,8 @@ dependencies = [ [[package]] name = "penumbra-funding" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "async-trait", @@ -2297,8 +2343,8 @@ dependencies = [ [[package]] name = "penumbra-governance" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2321,6 +2367,7 @@ dependencies = [ "once_cell", "pbjson-types", "penumbra-asset", + "penumbra-auction", "penumbra-community-pool", "penumbra-dex", "penumbra-distributions", @@ -2349,8 +2396,8 @@ dependencies = [ [[package]] name = "penumbra-ibc" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2382,8 +2429,8 @@ dependencies = [ [[package]] name = "penumbra-keys" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "aes", "anyhow", @@ -2426,8 +2473,8 @@ dependencies = [ [[package]] name = "penumbra-num" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2462,8 +2509,8 @@ dependencies = [ [[package]] name = "penumbra-proof-params" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ec", @@ -2488,8 +2535,8 @@ dependencies = [ [[package]] name = "penumbra-proto" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "async-trait", @@ -2515,8 +2562,8 @@ dependencies = [ [[package]] name = "penumbra-sct" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2546,8 +2593,8 @@ dependencies = [ [[package]] name = "penumbra-shielded-pool" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2565,6 +2612,7 @@ dependencies = [ "decaf377-fmd", "decaf377-ka", "decaf377-rdsa", + "futures", "hex", "ibc-types", "im", @@ -2593,8 +2641,8 @@ dependencies = [ [[package]] name = "penumbra-stake" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2635,8 +2683,8 @@ dependencies = [ [[package]] name = "penumbra-tct" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "ark-ed-on-bls12-377", "ark-ff", @@ -2663,8 +2711,8 @@ dependencies = [ [[package]] name = "penumbra-transaction" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "ark-ff", @@ -2686,6 +2734,7 @@ dependencies = [ "once_cell", "pbjson-types", "penumbra-asset", + "penumbra-auction", "penumbra-community-pool", "penumbra-dex", "penumbra-fee", @@ -2713,8 +2762,8 @@ dependencies = [ [[package]] name = "penumbra-txhash" -version = "0.73.0" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.73.0#6e88f8313428c7efc1713d8e2884f74b474fd497" +version = "0.71.0" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=c52969b5e#c52969b5ef6e2af83cad9b1670369ba8ae0fcde9" dependencies = [ "anyhow", "blake2b_simd 1.0.2", @@ -2736,6 +2785,7 @@ dependencies = [ "hex", "indexed_db_futures", "penumbra-asset", + "penumbra-auction", "penumbra-compact-block", "penumbra-dex", "penumbra-fee", diff --git a/packages/wasm/crate/Cargo.toml b/packages/wasm/crate/Cargo.toml index 67111b2cee..3473a59626 100644 --- a/packages/wasm/crate/Cargo.toml +++ b/packages/wasm/crate/Cargo.toml @@ -15,19 +15,22 @@ default = ["console_error_panic_hook"] mock-database = [] [dependencies] -penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-asset" } -penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-compact-block", default-features = false } -penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-dex", default-features = false } -penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-fee", default-features = false } -penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-keys" } -penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-num" } -penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-proof-params", default-features = false } -penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-proto", default-features = false } -penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-sct", default-features = false } -penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-shielded-pool", default-features = false } -penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-stake", default-features = false } -penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-tct" } -penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.73.0", package = "penumbra-transaction", default-features = false } +# TODO: Use `tag` instead of `rev` once auctions land in a tagged release of +# core. +penumbra-auction = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-auction", default-features = false } +penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-asset" } +penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-compact-block", default-features = false } +penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-dex", default-features = false } +penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-fee", default-features = false } +penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-keys" } +penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-num" } +penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-proof-params", default-features = false } +penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-proto", default-features = false } +penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-sct", default-features = false } +penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-shielded-pool", default-features = false } +penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-stake", default-features = false } +penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-tct" } +penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "c52969b5e", package = "penumbra-transaction", default-features = false } anyhow = "1.0.80" ark-ff = { version = "0.4.2", features = ["std"] } diff --git a/packages/wasm/crate/src/auction.rs b/packages/wasm/crate/src/auction.rs new file mode 100644 index 0000000000..5b04ab1578 --- /dev/null +++ b/packages/wasm/crate/src/auction.rs @@ -0,0 +1,70 @@ +use crate::{error::WasmResult, metadata::customize_symbol_inner, utils}; +use penumbra_asset::asset::Metadata; +use penumbra_auction::auction::{dutch::DutchAuctionDescription, AuctionId, AuctionNft}; +use penumbra_proto::DomainType; +use wasm_bindgen::prelude::wasm_bindgen; + +/// Given a `Uint8Array` encoding of a `DutchAuctionDescription`, returns that +/// auction's ID. +#[wasm_bindgen] +pub fn get_auction_id(description: &[u8]) -> WasmResult> { + utils::set_panic_hook(); + let description = DutchAuctionDescription::decode(description)?; + Ok(description.id().encode_to_vec()) +} + +/// Given a `Uint8Array` encoding of an `AuctionId` (along with a sequence +/// number), returns the metadata for the auction NFT describing that auction +/// and its current sequence number. +#[wasm_bindgen] +pub fn get_auction_nft_metadata(auction_id: &[u8], seq: u64) -> WasmResult> { + utils::set_panic_hook(); + let auction_id = AuctionId::decode(auction_id)?; + let nft = AuctionNft::new(auction_id, seq); + let metadata_proto = customize_symbol_inner(nft.metadata.to_proto())?; + let metadata_domain_type = Metadata::try_from(metadata_proto)?; + + Ok(metadata_domain_type.encode_to_vec()) +} + +#[cfg(test)] +mod tests { + use ark_ff::Zero; + use decaf377::Fq; + use penumbra_asset::{ + asset::{Id, Metadata}, + Value, + }; + use penumbra_auction::auction::dutch::DutchAuctionDescription; + use penumbra_num::Amount; + use penumbra_proto::DomainType; + + use crate::auction::get_auction_id; + + use super::get_auction_nft_metadata; + + #[test] + fn it_gets_correct_id_and_metadata() { + let description = DutchAuctionDescription { + start_height: 0, + end_height: 100, + input: Value { + amount: Amount::default(), + asset_id: Id(Fq::zero()), + }, + min_output: Amount::default(), + max_output: Amount::default(), + nonce: [0; 32], + output_id: Id(Fq::zero()), + step_count: 100u64, + }; + + let auction_id_bytes = get_auction_id(&description.encode_to_vec()).unwrap(); + let result_bytes = get_auction_nft_metadata(&auction_id_bytes, 1234).unwrap(); + let result = Metadata::decode::<&[u8]>(&result_bytes).unwrap(); + let result_proto = result.to_proto(); + + assert!(result_proto.symbol.starts_with("auction(")); + assert!(result_proto.display.starts_with("auctionnft_1234_pauctid1")); + } +} diff --git a/packages/wasm/crate/src/dex.rs b/packages/wasm/crate/src/dex.rs index 9de3c6e906..2ab63fae0c 100644 --- a/packages/wasm/crate/src/dex.rs +++ b/packages/wasm/crate/src/dex.rs @@ -22,7 +22,7 @@ pub fn compute_position_id(position: &[u8]) -> WasmResult> { /// Arguments: /// position_value: `lp::position::Position` /// position_state: `lp::position::State` -/// Returns: `Uint8Array representing a DenomMetadata` +/// Returns: `Uint8Array` representing a `Metadata` #[wasm_bindgen] pub fn get_lpnft_asset(position_id: &[u8], position_state: &[u8]) -> WasmResult> { utils::set_panic_hook(); diff --git a/packages/wasm/crate/src/lib.rs b/packages/wasm/crate/src/lib.rs index 8468ee2e05..d6772cbf5c 100644 --- a/packages/wasm/crate/src/lib.rs +++ b/packages/wasm/crate/src/lib.rs @@ -4,6 +4,7 @@ extern crate core; +pub mod auction; pub mod build; pub mod dex; pub mod error; diff --git a/packages/wasm/crate/src/metadata.rs b/packages/wasm/crate/src/metadata.rs index 054c966c33..2798a76313 100644 --- a/packages/wasm/crate/src/metadata.rs +++ b/packages/wasm/crate/src/metadata.rs @@ -9,6 +9,8 @@ use crate::{error::WasmResult, utils}; pub static UNBONDING_TOKEN_REGEX: &str = "^uunbonding_(?Pstart_at_(?P[0-9]+)_(?Ppenumbravalid1(?P[a-zA-HJ-NP-Z0-9]+)))$"; pub static DELEGATION_TOKEN_REGEX: &str = "^udelegation_(?Ppenumbravalid1(?P[a-zA-HJ-NP-Z0-9]+))$"; +pub static AUCTION_NFT_REGEX: &str = + "^auctionnft_(?P[a-z_0-9]+_pauctid1(?P[a-zA-HJ-NP-Z0-9]+))$"; pub static SHORTENED_ID_LENGTH: usize = 8; /// Given a binary-encoded `Metadata`, returns a new binary-encoded `Metadata` @@ -32,6 +34,7 @@ pub fn customize_symbol(metadata_bytes: &[u8]) -> WasmResult> { pub fn customize_symbol_inner(metadata: Metadata) -> WasmResult { let unbonding_re = Regex::new(UNBONDING_TOKEN_REGEX)?; let delegation_re = Regex::new(DELEGATION_TOKEN_REGEX)?; + let auction_re = Regex::new(AUCTION_NFT_REGEX)?; if let Some(captures) = unbonding_re.captures(&metadata.base) { let shortened_id = shorten_id(&captures)?; @@ -51,6 +54,13 @@ pub fn customize_symbol_inner(metadata: Metadata) -> WasmResult { symbol: format!("delUM({shortened_id}...)"), ..metadata }); + } else if let Some(captures) = auction_re.captures(&metadata.base) { + let shortened_id = shorten_id(&captures)?; + + return Ok(Metadata { + symbol: format!("auction({shortened_id}...)"), + ..metadata + }); } Ok(metadata) @@ -73,21 +83,32 @@ mod test_helpers { use super::*; - pub fn get_metadata_for(display_denom: &str) -> Metadata { + pub fn get_metadata_for(display_denom: &str, base_denom_is_display_denom: bool) -> Metadata { let mut denom_units = Vec::new(); denom_units.push(DenomUnit { aliases: Vec::new(), - denom: format!("u{display_denom}"), + denom: if base_denom_is_display_denom { + String::from(display_denom) + } else { + format!("u{display_denom}") + }, exponent: 0, }); - denom_units.push(DenomUnit { - aliases: Vec::new(), - denom: String::from(display_denom), - exponent: 6, - }); + + if !base_denom_is_display_denom { + denom_units.push(DenomUnit { + aliases: Vec::new(), + denom: String::from(display_denom), + exponent: 6, + }); + } Metadata { - base: format!("u{display_denom}"), + base: if base_denom_is_display_denom { + String::from(display_denom) + } else { + format!("u{display_denom}") + }, description: String::from(""), denom_units, display: String::from(display_denom), @@ -100,14 +121,14 @@ mod test_helpers { #[test] fn it_interpolates_display_denom() { - assert_eq!(get_metadata_for("penumbra").base, "upenumbra"); - assert_eq!(get_metadata_for("penumbra").display, "penumbra"); + assert_eq!(get_metadata_for("penumbra", false).base, "upenumbra"); + assert_eq!(get_metadata_for("penumbra", false).display, "penumbra"); assert_eq!( - get_metadata_for("penumbra").denom_units[0].denom, + get_metadata_for("penumbra", false).denom_units[0].denom, "upenumbra" ); assert_eq!( - get_metadata_for("penumbra").denom_units[1].denom, + get_metadata_for("penumbra", false).denom_units[1].denom, "penumbra" ); } @@ -122,7 +143,7 @@ mod customize_symbol_inner_tests { let metadata = Metadata { name: String::from("Penumbra"), symbol: String::from("UM"), - ..test_helpers::get_metadata_for("penumbra") + ..test_helpers::get_metadata_for("penumbra", false) }; let customized_metadata = customize_symbol_inner(metadata.clone()).unwrap(); @@ -134,7 +155,10 @@ mod customize_symbol_inner_tests { let metadata = Metadata { name: String::from("Unbonding Token"), symbol: String::from(""), - ..test_helpers::get_metadata_for("unbonding_start_at_1234_penumbravalid1abcdef123456") + ..test_helpers::get_metadata_for( + "unbonding_start_at_1234_penumbravalid1abcdef123456", + false, + ) }; let customized_metadata = customize_symbol_inner(metadata.clone()).unwrap(); @@ -146,12 +170,27 @@ mod customize_symbol_inner_tests { let metadata = Metadata { name: String::from("Delegation Token"), symbol: String::from(""), - ..test_helpers::get_metadata_for("delegation_penumbravalid1abcdef123456") + ..test_helpers::get_metadata_for("delegation_penumbravalid1abcdef123456", false) }; let customized_metadata = customize_symbol_inner(metadata.clone()).unwrap(); assert_eq!(customized_metadata.symbol, "delUM(abcdef12...)"); } + + #[test] + fn it_modifies_auction_nft_symbol() { + let metadata = Metadata { + name: String::from(""), + symbol: String::from(""), + ..test_helpers::get_metadata_for( + "auctionnft_0_pauctid1jqyupqnzznyfpq940mv0ac33pyx77s7af3kgdw4nstjmp3567dks8n5amh", + true, + ) + }; + let customized_metadata = customize_symbol_inner(metadata.clone()).unwrap(); + + assert_eq!(customized_metadata.symbol, "auction(jqyupqnz...)") + } } #[cfg(test)] @@ -167,7 +206,7 @@ mod customize_symbol_tests { let metadata = Metadata { name: String::from("Delegation Token"), symbol: String::from(""), - ..test_helpers::get_metadata_for("delegation_penumbravalid1abcdef123456") + ..test_helpers::get_metadata_for("delegation_penumbravalid1abcdef123456", false) }; let metadata_as_bytes = MetadataDomainType::try_from(metadata) .unwrap() diff --git a/packages/wasm/crate/src/tx.rs b/packages/wasm/crate/src/tx.rs index 831c7e16a7..41bae30314 100644 --- a/packages/wasm/crate/src/tx.rs +++ b/packages/wasm/crate/src/tx.rs @@ -298,21 +298,24 @@ pub async fn transaction_info_inner( match action_view { ActionView::Spend(SpendView::Visible { note, .. }) => { let address = note.address(); - address_views.insert(address, fvk.view_address(address)); + let address_view = fvk.view_address(address.clone()); + address_views.insert(address, address_view); asset_ids.insert(note.asset_id()); } ActionView::Output(OutputView::Visible { note, .. }) => { let address = note.address(); - address_views.insert(address, fvk.view_address(address)); + let address_view = fvk.view_address(address.clone()); + address_views.insert(address, address_view.clone()); asset_ids.insert(note.asset_id()); // Also add an AddressView for the return address in the memo. let memo = tx.decrypt_memo(&fvk)?; - address_views.insert(memo.return_address(), fvk.view_address(address)); + address_views.insert(memo.return_address(), address_view); } ActionView::Swap(SwapView::Visible { swap_plaintext, .. }) => { - let address = swap_plaintext.claim_address; - address_views.insert(address, fvk.view_address(address)); + let address = swap_plaintext.claim_address.clone(); + let address_view = fvk.view_address(address.clone()); + address_views.insert(address, address_view); asset_ids.insert(swap_plaintext.trading_pair.asset_1()); asset_ids.insert(swap_plaintext.trading_pair.asset_2()); } @@ -321,13 +324,15 @@ pub async fn transaction_info_inner( }) => { // Both will be sent to the same address so this only needs to be added once let address = output_1.address(); - address_views.insert(address, fvk.view_address(address)); + let address_view = fvk.view_address(address.clone()); + address_views.insert(address, address_view); asset_ids.insert(output_1.asset_id()); asset_ids.insert(output_2.asset_id()); } ActionView::DelegatorVote(DelegatorVoteView::Visible { note, .. }) => { let address = note.address(); - address_views.insert(address, fvk.view_address(address)); + let address_view = fvk.view_address(address.clone()); + address_views.insert(address, address_view); asset_ids.insert(note.asset_id()); } _ => {} diff --git a/packages/wasm/src/auction.ts b/packages/wasm/src/auction.ts new file mode 100644 index 0000000000..b7ca43bd78 --- /dev/null +++ b/packages/wasm/src/auction.ts @@ -0,0 +1,16 @@ +import { + AuctionId, + DutchAuctionDescription, +} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/auction/v1alpha1/auction_pb'; +import { get_auction_id, get_auction_nft_metadata } from '../wasm'; +import { Metadata } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb'; + +export const getAuctionId = (dutchAuctionDescription: DutchAuctionDescription): AuctionId => { + const result = get_auction_id(dutchAuctionDescription.toBinary()); + return AuctionId.fromBinary(result); +}; + +export const getAuctionNftMetadata = (auctionId: AuctionId, seq: bigint): Metadata => { + const result = get_auction_nft_metadata(auctionId.toBinary(), seq); + return Metadata.fromBinary(result); +};