From 49ae3ab2d76cb4a3c0a05d484940a56e49add434 Mon Sep 17 00:00:00 2001 From: Tal Derei <70081547+TalDerei@users.noreply.github.com> Date: Wed, 19 Feb 2025 14:54:30 -0800 Subject: [PATCH] web: LQT integration (#2041) * view service: LQT scaffolding (#2035) * view service: lqt voting notes scaffolding * view service: tournament votes rpc wip * wasm: extending planner with lqt voting action (#2037) * indexedDB: LQT historical votes table (#2038) * indexedDB: lqt historical votes table * workaround to normalize action cases * temporary measure for compilation purposes * linting * continue flushing out LQT integration * wasm: direct all available voting power to a single asset * wasm: informative expect messages * changeset * wasm: wasm bindgen tests * view server: expand vitest suite for new view service methods * wasm: clippy * protobuf: revert temporary workaround and consume fixed protos * action view: liquidity tournament visible and opaque views and translators * address feedback * linting * nit naming * refactor: account for LQT votes per delegation token * action views: fix opaque action view * more feedback * attempt to pass ci * satisfy linter * changeset * delete outdated changeset * stale ref; satisfy linter --- .changeset/calm-shirts-destroy.md | 12 + .../perspective/src/plan/view-action-plan.ts | 9 + .../src/transaction/classification.ts | 3 +- .../perspective/src/transaction/classify.ts | 4 + .../src/translators/action-view.ts | 9 + .../liquidity-tournament-vote-view.ts | 18 + packages/protobuf/package.json | 2 +- .../protobuf/src/services/penumbra-core.ts | 1 + packages/protobuf/src/web.ts | 4 +- packages/services/src/test-utils.ts | 8 + packages/services/src/view-service/index.ts | 4 + .../src/view-service/lqt-voting-notes.test.ts | 145 +++++ .../src/view-service/lqt-voting-notes.ts | 46 ++ .../src/view-service/tournament-votes.test.ts | 84 +++ .../src/view-service/tournament-votes.ts | 41 ++ packages/storage/src/indexed-db/config.ts | 2 +- packages/storage/src/indexed-db/index.ts | 82 +++ packages/types/src/indexed-db.ts | 35 ++ packages/types/src/querier.ts | 7 + .../components/ui/tx/action-view.tsx | 5 + .../liquidity-tournament-vote.tsx | 70 +++ packages/ui/src/ActionView/action-view.tsx | 2 + .../actions/liquidity-tournament-vote.tsx | 11 + packages/wasm/crate/Cargo.lock | 569 +++++++++--------- packages/wasm/crate/Cargo.toml | 35 +- packages/wasm/crate/src/lib.rs | 1 + packages/wasm/crate/src/planner.rs | 60 +- packages/wasm/crate/src/voting.rs | 39 ++ packages/wasm/crate/tests/test_build.rs | 1 + .../tests/test_liquidity_tournament_voting.rs | 362 +++++++++++ packages/wasm/crate/tests/test_metadata.rs | 1 + .../tests/test_planner_delegator_vote.rs | 3 + .../wasm/crate/tests/test_planner_general.rs | 1 + .../wasm/crate/tests/test_planner_send_max.rs | 9 + packages/wasm/crate/tests/test_witness.rs | 1 + packages/wasm/src/voting.ts | 23 + 36 files changed, 1399 insertions(+), 310 deletions(-) create mode 100644 .changeset/calm-shirts-destroy.md create mode 100644 packages/perspective/src/translators/liquidity-tournament-vote-view.ts create mode 100644 packages/services/src/view-service/lqt-voting-notes.test.ts create mode 100644 packages/services/src/view-service/lqt-voting-notes.ts create mode 100644 packages/services/src/view-service/tournament-votes.test.ts create mode 100644 packages/services/src/view-service/tournament-votes.ts create mode 100644 packages/ui-deprecated/components/ui/tx/actions-views/liquidity-tournament-vote.tsx create mode 100644 packages/ui/src/ActionView/actions/liquidity-tournament-vote.tsx create mode 100644 packages/wasm/crate/src/voting.rs create mode 100644 packages/wasm/crate/tests/test_liquidity_tournament_voting.rs create mode 100644 packages/wasm/src/voting.ts diff --git a/.changeset/calm-shirts-destroy.md b/.changeset/calm-shirts-destroy.md new file mode 100644 index 0000000000..6a506bbb78 --- /dev/null +++ b/.changeset/calm-shirts-destroy.md @@ -0,0 +1,12 @@ +--- +'@penumbra-zone/protobuf': major +'@penumbra-zone/services': major +'@penumbra-zone/storage': major +'@penumbra-zone/types': major +'@penumbra-zone/ui-deprecated': minor +'@penumbra-zone/perspective': minor +'@penumbra-zone/wasm': minor +'@penumbra-zone/ui': minor +--- + +LQT integration in web packages diff --git a/packages/perspective/src/plan/view-action-plan.ts b/packages/perspective/src/plan/view-action-plan.ts index e0906f7d4d..3766d30160 100644 --- a/packages/perspective/src/plan/view-action-plan.ts +++ b/packages/perspective/src/plan/view-action-plan.ts @@ -452,6 +452,15 @@ export const viewActionPlan = }); } + case 'actionLiquidityTournamentVote': { + return new ActionView({ + actionView: { + case: 'actionLiquidityTournamentVote', + value: {}, + }, + }); + } + case undefined: return new ActionView({ actionView: actionPlan.action, diff --git a/packages/perspective/src/transaction/classification.ts b/packages/perspective/src/transaction/classification.ts index 47824db83b..ca0ab56709 100644 --- a/packages/perspective/src/transaction/classification.ts +++ b/packages/perspective/src/transaction/classification.ts @@ -32,4 +32,5 @@ export type TransactionClassification = | 'positionRewardClaim' | 'communityPoolSpend' | 'communityPoolOutput' - | 'communityPoolDeposit'; + | 'communityPoolDeposit' + | 'liquidityTournamentVote'; diff --git a/packages/perspective/src/transaction/classify.ts b/packages/perspective/src/transaction/classify.ts index 288d8235c6..c8af67e358 100644 --- a/packages/perspective/src/transaction/classify.ts +++ b/packages/perspective/src/transaction/classify.ts @@ -78,6 +78,9 @@ export const classifyTransaction = (txv?: TransactionView): TransactionClassific if (allActionCases.has('communityPoolOutput')) { return 'communityPoolOutput'; } + if (allActionCases.has('actionLiquidityTournamentVote')) { + return 'liquidityTournamentVote'; + } const hasOpaqueSpend = txv.bodyView?.actionViews.some( a => a.actionView.case === 'spend' && a.actionView.value.spendView.case === 'opaque', @@ -167,6 +170,7 @@ export const TRANSACTION_LABEL_BY_CLASSIFICATION: Record diff --git a/packages/perspective/src/translators/action-view.ts b/packages/perspective/src/translators/action-view.ts index 6d21d86e58..aa1ee91901 100644 --- a/packages/perspective/src/translators/action-view.ts +++ b/packages/perspective/src/translators/action-view.ts @@ -6,6 +6,7 @@ import { Address } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; import { asOpaqueSwapView } from './swap-view.js'; import { asOpaqueSwapClaimView } from './swap-claim-view.js'; import { asOpaqueDelegatorVoteView } from './delegator-vote-view.js'; +import { asOpaqueLiquidityTournamentVoteView } from './liquidity-tournament-vote-view.js'; export const asPublicActionView: Translator = actionView => { switch (actionView?.actionView.case) { @@ -49,6 +50,14 @@ export const asPublicActionView: Translator = actionView => { }, }); + case 'actionLiquidityTournamentVote': + return new ActionView({ + actionView: { + case: 'actionLiquidityTournamentVote', + value: asOpaqueLiquidityTournamentVoteView(actionView.actionView.value), + }, + }); + // Currently defaulting to displaying that all data is public as it's better // to err on communicating private data as public than the other way around // TODO: Do proper audit of what data for each action is public diff --git a/packages/perspective/src/translators/liquidity-tournament-vote-view.ts b/packages/perspective/src/translators/liquidity-tournament-vote-view.ts new file mode 100644 index 0000000000..4d710dd783 --- /dev/null +++ b/packages/perspective/src/translators/liquidity-tournament-vote-view.ts @@ -0,0 +1,18 @@ +import { + ActionLiquidityTournamentVoteView, + ActionLiquidityTournamentVoteView_Opaque, +} from '@penumbra-zone/protobuf/penumbra/core/component/funding/v1/funding_pb'; +import { Translator } from './types.js'; + +export const asOpaqueLiquidityTournamentVoteView: Translator< + ActionLiquidityTournamentVoteView +> = liquidityTournamentVoteView => { + return new ActionLiquidityTournamentVoteView({ + liquidityTournamentVote: { + case: 'opaque', + value: new ActionLiquidityTournamentVoteView_Opaque({ + vote: liquidityTournamentVoteView?.liquidityTournamentVote.value?.vote, + }), + }, + }); +}; diff --git a/packages/protobuf/package.json b/packages/protobuf/package.json index 72e7fd6d43..d4422a83b1 100644 --- a/packages/protobuf/package.json +++ b/packages/protobuf/package.json @@ -16,7 +16,7 @@ "gen:ibc": "buf generate buf.build/cosmos/ibc:7ab44ae956a0488ea04e04511efa5f70", "gen:ics23": "buf generate buf.build/cosmos/ics23:55085f7c710a45f58fa09947208eb70b", "gen:noble": "buf generate buf.build/noble-assets/forwarding:5a8609a6772d417584a9c60cd8b80881", - "gen:penumbra": "buf generate buf.build/penumbra-zone/penumbra:adb116eefae84c1abd53a1594b895360", + "gen:penumbra": "buf generate buf.build/penumbra-zone/penumbra:ae2300bce202a7d429727f1340e7412d3b9f810c", "lint": "eslint src", "lint:fix": "eslint src --fix", "lint:strict": "tsc --noEmit && eslint src --max-warnings 0", diff --git a/packages/protobuf/src/services/penumbra-core.ts b/packages/protobuf/src/services/penumbra-core.ts index 12bc643396..1609f13a90 100644 --- a/packages/protobuf/src/services/penumbra-core.ts +++ b/packages/protobuf/src/services/penumbra-core.ts @@ -12,3 +12,4 @@ export { QueryService as GovernanceService } from '../../gen/penumbra/core/compo export { QueryService as SctService } from '../../gen/penumbra/core/component/sct/v1/sct_connect.js'; export { QueryService as ShieldedPoolService } from '../../gen/penumbra/core/component/shielded_pool/v1/shielded_pool_connect.js'; export { QueryService as StakeService } from '../../gen/penumbra/core/component/stake/v1/stake_connect.js'; +export { FundingService } from '../../gen/penumbra/core/component/funding/v1/funding_connect.js'; diff --git a/packages/protobuf/src/web.ts b/packages/protobuf/src/web.ts index 10ec114021..1c8b74dbfc 100644 --- a/packages/protobuf/src/web.ts +++ b/packages/protobuf/src/web.ts @@ -9,6 +9,7 @@ import { import type { CustodyService } from './services/penumbra-custody.js'; import type { ViewService } from './services/penumbra-view.js'; import type { + FundingService, AppService, AuctionService, CommunityPoolService, @@ -43,4 +44,5 @@ export type PenumbraService = | typeof SimulationService | typeof StakeService | typeof TendermintProxyService - | typeof ViewService; + | typeof ViewService + | typeof FundingService; diff --git a/packages/services/src/test-utils.ts b/packages/services/src/test-utils.ts index 0537787b8a..e5e31ce059 100644 --- a/packages/services/src/test-utils.ts +++ b/packages/services/src/test-utils.ts @@ -36,6 +36,9 @@ export interface IndexedDbMock { saveGasPrices?: Mock; saveTransactionInfo?: Mock; getTransactionInfo?: Mock; + getBlockHeightByEpoch?: Mock; + saveLQTHistoricalVote?: Mock; + getLQTHistoricalVotes?: Mock; } export interface AuctionMock { @@ -62,6 +65,11 @@ export interface MockQuerier { sct?: SctMock; shieldedPool?: ShieldedPoolMock; stake?: StakeMock; + funding?: FundingMock; +} + +export interface FundingMock { + lqtCheckNullifier?: Mock; } export interface SctMock { diff --git a/packages/services/src/view-service/index.ts b/packages/services/src/view-service/index.ts index 9f5dfa7149..919b268a13 100644 --- a/packages/services/src/view-service/index.ts +++ b/packages/services/src/view-service/index.ts @@ -31,6 +31,8 @@ import { witness } from './witness.js'; import { witnessAndBuild } from './witness-and-build.js'; import { transparentAddress } from './transparent-address.js'; import { latestSwaps } from './latest-swaps.js'; +import { lqtVotingNotes } from './lqt-voting-notes.js'; +import { tournamentVotes } from './tournament-votes.js'; export type Impl = ServiceImpl; @@ -66,4 +68,6 @@ export const viewImpl: Impl = { witnessAndBuild, transparentAddress, latestSwaps, + lqtVotingNotes, + tournamentVotes, }; diff --git a/packages/services/src/view-service/lqt-voting-notes.test.ts b/packages/services/src/view-service/lqt-voting-notes.test.ts new file mode 100644 index 0000000000..bdf3b8f148 --- /dev/null +++ b/packages/services/src/view-service/lqt-voting-notes.test.ts @@ -0,0 +1,145 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { + LqtVotingNotesRequest, + LqtVotingNotesResponse, + NotesForVotingResponse, +} from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; +import { ViewService } from '@penumbra-zone/protobuf'; +import { servicesCtx } from '../ctx/prax.js'; +import { IndexedDbMock, MockQuerier, MockServices } from '../test-utils.js'; +import type { ServicesInterface } from '@penumbra-zone/types/services'; +import { lqtVotingNotes } from './lqt-voting-notes.js'; +import { Epoch } from '@penumbra-zone/protobuf/penumbra/core/component/sct/v1/sct_pb'; +import { LqtCheckNullifierResponse } from '@penumbra-zone/protobuf/penumbra/core/component/funding/v1/funding_pb'; +import { TransactionId } from '@penumbra-zone/protobuf/penumbra/core/txhash/v1/txhash_pb'; + +describe('lqtVotingNotes request handler', () => { + let mockServices: MockServices; + let mockIndexedDb: IndexedDbMock; + let mockQuerier: MockQuerier; + let mockCtx: HandlerContext; + + beforeEach(() => { + vi.resetAllMocks(); + + mockIndexedDb = { + getLQTHistoricalVotes: vi.fn(), + getBlockHeightByEpoch: vi.fn(), + getNotesForVoting: vi.fn(), + }; + + mockServices = { + getWalletServices: vi.fn(() => + Promise.resolve({ indexedDb: mockIndexedDb }), + ) as MockServices['getWalletServices'], + }; + + mockCtx = createHandlerContext({ + service: ViewService, + method: ViewService.methods.lqtVotingNotes, + protocolName: 'mock', + requestMethod: 'MOCK', + url: '/mock', + contextValues: createContextValues().set(servicesCtx, () => + Promise.resolve(mockServices as unknown as ServicesInterface), + ), + }); + }); + + test('returns no voting notes if the nullifier has already been used for voting in the current epoch', async () => { + // voting notes mocked with static data, and the mock bypasses the logic in the real implementation, + // but that's fine. + mockIndexedDb.getNotesForVoting?.mockResolvedValueOnce(testData); + mockIndexedDb.getBlockHeightByEpoch?.mockResolvedValueOnce(epoch); + + mockQuerier = { + funding: { + lqtCheckNullifier: vi.fn().mockResolvedValue( + new LqtCheckNullifierResponse({ + transaction: new TransactionId({ + inner: new Uint8Array([]), + }), + alreadyVoted: true, + epochIndex: 100n, + }), + ), + }, + }; + + mockServices = { + getWalletServices: vi.fn(() => + Promise.resolve({ indexedDb: mockIndexedDb, querier: mockQuerier }), + ) as MockServices['getWalletServices'], + }; + + const responses: LqtVotingNotesResponse[] = []; + const req = new LqtVotingNotesRequest({}); + for await (const res of lqtVotingNotes(req, mockCtx)) { + responses.push(new LqtVotingNotesResponse(res)); + } + + expect(responses.length).toBe(0); + }); + + test('returns voting notes when the nullifier has not been used for voting in the current epoch', async () => { + mockIndexedDb.getNotesForVoting?.mockResolvedValueOnce(testData); + mockIndexedDb.getBlockHeightByEpoch?.mockResolvedValueOnce(epoch); + + mockQuerier = { + funding: { + lqtCheckNullifier: vi.fn().mockResolvedValue( + new LqtCheckNullifierResponse({ + transaction: new TransactionId({ + inner: new Uint8Array([]), + }), + alreadyVoted: false, + epochIndex: 100n, + }), + ), + }, + }; + + mockServices = { + getWalletServices: vi.fn(() => + Promise.resolve({ indexedDb: mockIndexedDb, querier: mockQuerier }), + ) as MockServices['getWalletServices'], + }; + + const responses: LqtVotingNotesResponse[] = []; + const req = new LqtVotingNotesRequest({}); + for await (const res of lqtVotingNotes(req, mockCtx)) { + responses.push(new LqtVotingNotesResponse(res)); + } + + expect(responses.length).toBe(2); + }); +}); + +const testData: NotesForVotingResponse[] = [ + NotesForVotingResponse.fromJson({ + noteRecord: { + noteCommitment: { + inner: 'pXS1k2kvlph+vuk9uhqeoP1mZRc+f526a06/bg3EBwQ=', + }, + }, + identityKey: { + ik: 'VAv+z5ieJk7AcAIJoVIqB6boOj0AhZB2FKWsEidfvAE=', + }, + }), + NotesForVotingResponse.fromJson({ + noteRecord: { + noteCommitment: { + inner: '2XS1k2kvlph+vuk9uhqeoP1mZRc+f526a06/bg3EBwQ=', + }, + }, + identityKey: { + ik: 'pkxdxOn9EMqdjoCJdEGBKA8XY9P9RK9XmurIly/9yBA=', + }, + }), +]; + +const epoch = new Epoch({ + index: 100n, + startHeight: 5000n, +}); diff --git a/packages/services/src/view-service/lqt-voting-notes.ts b/packages/services/src/view-service/lqt-voting-notes.ts new file mode 100644 index 0000000000..d67ee9fbde --- /dev/null +++ b/packages/services/src/view-service/lqt-voting-notes.ts @@ -0,0 +1,46 @@ +import type { Impl } from './index.js'; +import { servicesCtx } from '../ctx/prax.js'; +import { notesForVoting } from './notes-for-voting.js'; +import { + LqtVotingNotesResponse, + NotesForVotingRequest, + SpendableNoteRecord, +} from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { Nullifier } from '@penumbra-zone/protobuf/penumbra/core/component/sct/v1/sct_pb'; + +export const lqtVotingNotes: Impl['lqtVotingNotes'] = async function* (req, ctx) { + const services = await ctx.values.get(servicesCtx)(); + const { indexedDb, querier } = await services.getWalletServices(); + + // Get the starting block height for the corresponding epoch index. + const epoch = await indexedDb.getBlockHeightByEpoch(req.epochIndex); + + // Retrieve SNRs from storage ('ASSETS' in IndexedDB) for the specified subaccount that are eligible for voting + // at the start height of the current epoch. Alternatively, a wasm helper `get_voting_notes` can be used to + // perform the same function. + const notesForVotingRequest = new NotesForVotingRequest({ + addressIndex: req.accountFilter, + votableAtHeight: epoch?.startHeight, + }); + const votingNotes = notesForVoting(notesForVotingRequest, ctx); + + // Iterate through each voting note and check if it has already been used for voting + // by performing a nullifier point query against the rpc provided by the funding service. + for await (const votingNote of votingNotes) { + if (!votingNote.noteRecord || !epoch?.index) { + continue; + } + const lqtCheckNullifierResponse = await querier.funding.lqtCheckNullifier( + epoch.index, + votingNote.noteRecord.nullifier as Nullifier, + ); + if (lqtCheckNullifierResponse.alreadyVoted) { + continue; + } + + const noteRecord = votingNote.noteRecord as SpendableNoteRecord; + + // Yield the SNRs that haven't been used for voting yet. + yield new LqtVotingNotesResponse({ noteRecord }); + } +}; diff --git a/packages/services/src/view-service/tournament-votes.test.ts b/packages/services/src/view-service/tournament-votes.test.ts new file mode 100644 index 0000000000..8ee437c1e7 --- /dev/null +++ b/packages/services/src/view-service/tournament-votes.test.ts @@ -0,0 +1,84 @@ +import { beforeEach, describe, expect, test, vi } from 'vitest'; +import { TournamentVotesRequest } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { createContextValues, createHandlerContext, HandlerContext } from '@connectrpc/connect'; +import { ViewService } from '@penumbra-zone/protobuf'; +import { servicesCtx } from '../ctx/prax.js'; +import { IndexedDbMock, MockServices } from '../test-utils.js'; +import type { ServicesInterface } from '@penumbra-zone/types/services'; +import { Epoch } from '@penumbra-zone/protobuf/penumbra/core/component/sct/v1/sct_pb'; +import { tournamentVotes } from './tournament-votes.js'; +import { Amount } from '@penumbra-zone/protobuf/penumbra/core/num/v1/num_pb'; +import { Value } from '@bufbuild/protobuf'; + +describe('tournamentVotes request handler', () => { + let mockServices: MockServices; + let mockIndexedDb: IndexedDbMock; + let mockCtx: HandlerContext; + + beforeEach(() => { + vi.resetAllMocks(); + + mockIndexedDb = { + getLQTHistoricalVotes: vi.fn(), + saveLQTHistoricalVote: vi.fn(), + getBlockHeightByEpoch: vi.fn(), + getNotesForVoting: vi.fn(), + }; + + mockServices = { + getWalletServices: vi.fn(() => + Promise.resolve({ indexedDb: mockIndexedDb }), + ) as MockServices['getWalletServices'], + }; + + mockCtx = createHandlerContext({ + service: ViewService, + method: ViewService.methods.tournamentVotes, + protocolName: 'mock', + requestMethod: 'MOCK', + url: '/mock', + contextValues: createContextValues().set(servicesCtx, () => + Promise.resolve(mockServices as unknown as ServicesInterface), + ), + }); + }); + + test('returns historical liquidity tournament votes that have been previously been saved to storage', async () => { + mockIndexedDb.getBlockHeightByEpoch?.mockResolvedValueOnce(epoch); + mockIndexedDb.saveLQTHistoricalVote?.mockResolvedValueOnce(mockVote); + mockIndexedDb.getLQTHistoricalVotes?.mockResolvedValueOnce([mockVote]); + + const req = new TournamentVotesRequest({}); + const vote = await tournamentVotes(req, mockCtx); + + expect(vote.votes?.length).toBe(1); + }); +}); + +const epoch = new Epoch({ + index: 100n, + startHeight: 5000n, +}); + +const mockVote = { + id: 'test-uuid-or-any-string', + epoch: 100n, + TransactionId: { + inner: new Uint8Array([1, 2, 3, 4]), + }, + AssetMetadata: { + penumbraAssetId: { + inner: new Uint8Array(new Array(32).fill(1)), + }, + }, + VoteValue: Value.fromJson({ + amount: { + lo: '1000', + hi: '0', + }, + }), + RewardValue: Amount.fromJson({ + lo: '500', + hi: '0', + }), +}; diff --git a/packages/services/src/view-service/tournament-votes.ts b/packages/services/src/view-service/tournament-votes.ts new file mode 100644 index 0000000000..07529bd463 --- /dev/null +++ b/packages/services/src/view-service/tournament-votes.ts @@ -0,0 +1,41 @@ +import type { Impl } from './index.js'; +import { + TournamentVotesResponse, + TournamentVotesResponse_Vote, +} from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { servicesCtx } from '../ctx/prax.js'; +import { Amount } from '@penumbra-zone/protobuf/penumbra/core/num/v1/num_pb'; +import { Value } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; + +export const tournamentVotes: Impl['tournamentVotes'] = async (req, ctx) => { + const services = await ctx.values.get(servicesCtx)(); + const { indexedDb } = await services.getWalletServices(); + + // Get the starting block height for the corresponding epoch index. + const epoch = await indexedDb.getBlockHeightByEpoch(req.epochIndex); + + // Retrieve the vote cast in the liquidity tournament for the current epoch. + const tournamentVote = new TournamentVotesResponse(); + if (epoch?.index) { + const votes = await indexedDb.getLQTHistoricalVotes(epoch.index); + + if (votes.length > 0) { + tournamentVote.votes = votes.map( + vote => + new TournamentVotesResponse_Vote({ + transaction: vote.TransactionId, + incentivizedAsset: vote.AssetMetadata.penumbraAssetId, + votePower: vote.VoteValue.amount, + reward: vote.RewardValue + ? new Value({ + amount: new Amount({ lo: vote.RewardValue.lo, hi: vote.RewardValue.hi }), + assetId: indexedDb.stakingTokenAssetId, + }) + : undefined, + }), + ); + } + } + + return tournamentVote; +}; diff --git a/packages/storage/src/indexed-db/config.ts b/packages/storage/src/indexed-db/config.ts index 51ac19c28e..b98ccb60a3 100644 --- a/packages/storage/src/indexed-db/config.ts +++ b/packages/storage/src/indexed-db/config.ts @@ -2,4 +2,4 @@ * The version number for the IndexedDB schema. This version number is used to manage * database upgrades and ensure that the correct schema version is applied. */ -export const IDB_VERSION = 48; +export const IDB_VERSION = 49; diff --git a/packages/storage/src/indexed-db/index.ts b/packages/storage/src/indexed-db/index.ts index 84a7b5bd08..23fac4b4a5 100644 --- a/packages/storage/src/indexed-db/index.ts +++ b/packages/storage/src/indexed-db/index.ts @@ -166,6 +166,9 @@ export class IndexedDb implements IndexedDbInterface { db.createObjectStore('AUCTIONS'); db.createObjectStore('AUCTION_OUTSTANDING_RESERVES'); db.createObjectStore('REGISTRY_VERSION'); + db.createObjectStore('LQT_HISTORICAL_VOTES', { + keyPath: 'id', + }).createIndex('epoch', 'epoch'); }, }); const constants = { @@ -410,6 +413,67 @@ export class IndexedDb implements IndexedDbInterface { }); } + /** + * Retrieves liquidity tournament votes and rewards for a given epoch. + */ + async getLQTHistoricalVotes(epoch: bigint): Promise< + { + TransactionId: TransactionId; + AssetMetadata: Metadata; + VoteValue: Value; + RewardValue: Amount | undefined; + id: string | undefined; + }[] + > { + const tournamentVotes = await this.db.getAllFromIndex( + 'LQT_HISTORICAL_VOTES', + 'epoch', + epoch.toString(), + ); + + return tournamentVotes.map(tournamentVote => ({ + TransactionId: TransactionId.fromJson(tournamentVote.TransactionId, { typeRegistry }), + AssetMetadata: Metadata.fromJson(tournamentVote.AssetMetadata, { typeRegistry }), + VoteValue: Value.fromJson(tournamentVote.VoteValue, { typeRegistry }), + RewardValue: tournamentVote.RewardValue + ? Amount.fromJson(tournamentVote.RewardValue, { typeRegistry }) + : undefined, + id: tournamentVote.id, + })); + } + + /** + * Saves historical liquidity tournament votes and rewards for a given epoch. + */ + async saveLQTHistoricalVote( + epoch: bigint, + transactionId: TransactionId, + assetMetadata: Metadata, + voteValue: Value, + rewardValue?: Amount, + id?: string, + ): Promise { + assertTransactionId(transactionId); + + // This is a unique identifier to force unique primary keys. If the field isn't provided, + // a random one is generated and used to store an object. + const uniquePrimaryKey = id ?? crypto.randomUUID(); + + const tournamentVote = { + epoch: epoch.toString(), + TransactionId: transactionId.toJson({ typeRegistry }) as Jsonified, + AssetMetadata: assetMetadata.toJson({ typeRegistry }) as Jsonified, + VoteValue: voteValue.toJson({ typeRegistry }) as Jsonified, + RewardValue: rewardValue ? (rewardValue.toJson({ typeRegistry }) as Jsonified) : null, + id: uniquePrimaryKey, + }; + + await this.u.update({ + table: 'LQT_HISTORICAL_VOTES', + value: tournamentVote, + }); + } + async getTransaction(txId: TransactionId): Promise { assertTransactionId(txId); const key = uint8ArrayToBase64(txId.inner); @@ -740,6 +804,24 @@ export class IndexedDb implements IndexedDbInterface { return epoch; } + /** + * Get the block height for the correspinding epoch index. + */ + async getBlockHeightByEpoch(epoch_index: bigint): Promise { + let epoch: Epoch | undefined; + + // Iterate over epochs and return the one with the matching epoch index. + for await (const cursor of this.db.transaction('EPOCHS', 'readonly').store) { + const currentEpoch = Epoch.fromJson(cursor.value); + if (currentEpoch.index === epoch_index) { + epoch = currentEpoch; + break; + } + } + + return epoch; + } + /** * Inserts the validator info into the database, or updates an existing * validator info if one with the same identity key exists. diff --git a/packages/types/src/indexed-db.ts b/packages/types/src/indexed-db.ts index b0f2dc49a6..f56135306a 100644 --- a/packages/types/src/indexed-db.ts +++ b/packages/types/src/indexed-db.ts @@ -111,6 +111,7 @@ export interface IndexedDbInterface { ): Promise; addEpoch(startHeight: bigint): Promise; getEpochByHeight(height: bigint): Promise; + getBlockHeightByEpoch(epoch_index: bigint): Promise; upsertValidatorInfo(validatorInfo: ValidatorInfo): Promise; iterateValidatorInfos(): AsyncGenerator; clearValidatorInfos(): Promise; @@ -176,6 +177,25 @@ export interface IndexedDbInterface { >; getPosition(positionId: PositionId): Promise; + + saveLQTHistoricalVote( + epoch: bigint, + transactionId: TransactionId, + assetMetadata: Metadata, + voteValue: Value, + rewardValue?: Amount, + id?: string | undefined, + ): Promise; + + getLQTHistoricalVotes(epoch: bigint): Promise< + { + TransactionId: TransactionId; + AssetMetadata: Metadata; + VoteValue: Value; + RewardValue: Amount | undefined; + id: string | undefined; + }[] + >; } export interface PenumbraDb extends DBSchema { @@ -324,6 +344,20 @@ export interface PenumbraDb extends DBSchema { output: Jsonified; }; }; + LQT_HISTORICAL_VOTES: { + key: string; + value: { + id: string; + epoch: string; + TransactionId: Jsonified; + AssetMetadata: Jsonified; + VoteValue: Jsonified; + RewardValue: Jsonified | null; + }; + indexes: { + epoch: string; + }; + }; } // need to store PositionId and Position in the same table @@ -361,4 +395,5 @@ export const IDB_TABLES: Tables = { tree_commitments: 'TREE_COMMITMENTS', tree_last_position: 'TREE_LAST_POSITION', tree_last_forgotten: 'TREE_LAST_FORGOTTEN', + lqt_historical_votes: 'LQT_HISTORICAL_VOTES', }; diff --git a/packages/types/src/querier.ts b/packages/types/src/querier.ts index 7455b15349..9832147d3e 100644 --- a/packages/types/src/querier.ts +++ b/packages/types/src/querier.ts @@ -9,7 +9,9 @@ import { DutchAuction, } from '@penumbra-zone/protobuf/penumbra/core/component/auction/v1/auction_pb'; import { CompactBlock } from '@penumbra-zone/protobuf/penumbra/core/component/compact_block/v1/compact_block_pb'; +import { LqtCheckNullifierResponse } from '@penumbra-zone/protobuf/penumbra/core/component/funding/v1/funding_pb'; import { + Nullifier, TimestampByHeightRequest, TimestampByHeightResponse, } from '@penumbra-zone/protobuf/penumbra/core/component/sct/v1/sct_pb'; @@ -35,6 +37,7 @@ export interface RootQuerierInterface { sct: SctQuerierInterface; cnidarium: CnidariumQuerierInterface; auction: AuctionQuerierInterface; + funding: FundingQuerierInterface; } export interface AppQuerierInterface { @@ -83,3 +86,7 @@ export interface AuctionQuerierInterface { export interface SctQuerierInterface { timestampByHeight(req: TimestampByHeightRequest): Promise; } + +export interface FundingQuerierInterface { + lqtCheckNullifier(epochIndex: bigint, nullifier: Nullifier): Promise; +} diff --git a/packages/ui-deprecated/components/ui/tx/action-view.tsx b/packages/ui-deprecated/components/ui/tx/action-view.tsx index b32f040d9c..52745c1733 100644 --- a/packages/ui-deprecated/components/ui/tx/action-view.tsx +++ b/packages/ui-deprecated/components/ui/tx/action-view.tsx @@ -18,6 +18,7 @@ import { PositionOpenComponent } from './actions-views/position-open.tsx'; import { PositionCloseComponent } from './actions-views/position-close.tsx'; import { PositionWithdrawComponent } from './actions-views/position-withdraw.tsx'; import { IbcRelayComponent } from './actions-views/ibc-relay.tsx'; +import { LiquidityTournamentVoteComponent } from './actions-views/liquidity-tournament-vote.tsx'; type Case = Exclude; @@ -47,6 +48,7 @@ const CASE_TO_LABEL: Record = { communityPoolDeposit: 'Community Pool Deposit', communityPoolOutput: 'Community Pool Output', communityPoolSpend: 'Community Pool Spend', + actionLiquidityTournamentVote: 'Liquidity Tournament Vote', }; const getLabelForActionCase = (actionCase: ActionView['actionView']['case']): string => { @@ -145,6 +147,9 @@ export const ActionViewComponent = ({ case 'communityPoolDeposit': return ; + case 'actionLiquidityTournamentVote': + return ; + default: return ; } diff --git a/packages/ui-deprecated/components/ui/tx/actions-views/liquidity-tournament-vote.tsx b/packages/ui-deprecated/components/ui/tx/actions-views/liquidity-tournament-vote.tsx new file mode 100644 index 0000000000..5c289fd7a1 --- /dev/null +++ b/packages/ui-deprecated/components/ui/tx/actions-views/liquidity-tournament-vote.tsx @@ -0,0 +1,70 @@ +import { ViewBox } from '../viewbox'; +import { ValueViewComponent } from '../../value'; +import { ActionLiquidityTournamentVoteView } from '@penumbra-zone/protobuf/penumbra/core/component/funding/v1/funding_pb'; +import { ActionDetails } from './action-details'; +import { ValueView } from '@penumbra-zone/protobuf/penumbra/core/asset/v1/asset_pb'; +import { ValueWithAddress } from './value-with-address'; +import { getAddress } from '@penumbra-zone/getters/note-view'; + +let globalValueView: ValueView | undefined; + +export const LiquidityTournamentVoteComponent = ({ + value, +}: { + value: ActionLiquidityTournamentVoteView; +}) => { + if (value.liquidityTournamentVote.case === 'visible') { + globalValueView = value.liquidityTournamentVote.value.note?.value; + const address = getAddress(value.liquidityTournamentVote.value.note); + + // Note: LQT action view is currently implemented in the deprecated-ui library + // for testng purposes, and the actual implementation will display all the relevant + // fields that are lacking here. + return ( + + + + + {value.liquidityTournamentVote.value.vote && ( + + + + )} + {value.liquidityTournamentVote.value.vote?.body?.incentivized && ( + + {value.liquidityTournamentVote.value.vote.body.incentivized.denom} + + )} + + } + /> + ); + } + + if (value.liquidityTournamentVote.case === 'opaque') { + return ( + + { + + + + } + {value.liquidityTournamentVote.value.vote?.body?.incentivized && ( + + {value.liquidityTournamentVote.value.vote.body.incentivized.denom} + + )} + + } + /> + ); + } + + return
Invalid ActionLiquidityTournamentVoteView
; +}; diff --git a/packages/ui/src/ActionView/action-view.tsx b/packages/ui/src/ActionView/action-view.tsx index 61d79ac599..804c4a8884 100644 --- a/packages/ui/src/ActionView/action-view.tsx +++ b/packages/ui/src/ActionView/action-view.tsx @@ -28,6 +28,7 @@ import { DutchAuctionWithdrawAction } from './actions/dutch-auction-withdraw'; import { CommunityPoolDepositAction } from './actions/community-pool-deposit'; import { CommunityPoolOutputAction } from './actions/community-pool-output'; import { CommunityPoolSpendAction } from './actions/community-pool-spend'; +import { LiquidityTournamentVoteAction } from './actions/liquidity-tournament-vote'; export interface ActionViewProps { action: ActionViewMessage; @@ -60,6 +61,7 @@ const componentMap = { communityPoolDeposit: CommunityPoolDepositAction, communityPoolOutput: CommunityPoolOutputAction, communityPoolSpend: CommunityPoolSpendAction, + actionLiquidityTournamentVote: LiquidityTournamentVoteAction, unknown: UnknownAction, } as const satisfies Record; diff --git a/packages/ui/src/ActionView/actions/liquidity-tournament-vote.tsx b/packages/ui/src/ActionView/actions/liquidity-tournament-vote.tsx new file mode 100644 index 0000000000..72397ceac7 --- /dev/null +++ b/packages/ui/src/ActionView/actions/liquidity-tournament-vote.tsx @@ -0,0 +1,11 @@ +import { UnknownAction } from './unknown'; +import { ActionLiquidityTournamentVote } from '@penumbra-zone/protobuf/penumbra/core/component/funding/v1/funding_pb'; + +export interface LiquidityTournamentVoteActionProps { + value: ActionLiquidityTournamentVote; +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars -- unimplemented +export const LiquidityTournamentVoteAction = (_: LiquidityTournamentVoteActionProps) => { + return ; +}; diff --git a/packages/wasm/crate/Cargo.lock b/packages/wasm/crate/Cargo.lock index d2c4c1787d..1bf2e93a13 100644 --- a/packages/wasm/crate/Cargo.lock +++ b/packages/wasm/crate/Cargo.lock @@ -666,6 +666,18 @@ version = "0.8.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "773648b94d0e5d620f64f280777445740e61fe701025087ec8b57f45c791888b" +[[package]] +name = "cosmos-sdk-proto" +version = "0.26.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "462e1f6a8e005acc8835d32d60cbd7973ed65ea2a8d8473830e675f050956427" +dependencies = [ + "informalsystems-pbjson", + "prost", + "serde", + "tendermint-proto", +] + [[package]] name = "cpufeatures" version = "0.2.14" @@ -804,8 +816,8 @@ dependencies = [ [[package]] name = "decaf377-fmd" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "ark-ff", "ark-serialize", @@ -818,8 +830,8 @@ dependencies = [ [[package]] name = "decaf377-ka" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "ark-ff", "decaf377", @@ -1031,16 +1043,6 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "b90ca2580b73ab6a1f724b76ca11ab632df820fd6040c336200d2c1df7b3c82c" -[[package]] -name = "eyre" -version = "0.6.12" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7cd915d99f24784cdc19fd37ef22b97e3ff0ae756c7e492e9fbfe897d61e2aec" -dependencies = [ - "indenter", - "once_cell", -] - [[package]] name = "f4jumble" version = "0.1.1" @@ -1102,7 +1104,6 @@ version = "0.4.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c606d892c9de11507fa0dcffc116434f94e105d0bbdc4e405b61519464c49d7b" dependencies = [ - "eyre", "paste", ] @@ -1337,12 +1338,13 @@ dependencies = [ [[package]] name = "ibc-proto" -version = "0.41.0" +version = "0.51.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "dd4ee32b22d3b06f31529b956f4928e5c9a068d71e46cf6abfa19c31ca550553" +checksum = "9b70f517162e74e2d35875b8b94bf4d1e45f2c69ef3de452dc855944455d33ca" dependencies = [ - "base64 0.21.7", + "base64 0.22.1", "bytes", + "cosmos-sdk-proto", "flex-error", "ics23", "informalsystems-pbjson", @@ -1354,9 +1356,9 @@ dependencies = [ [[package]] name = "ibc-types" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f45534bc1118d30f6537040bdf822f17245dcb5467a14094070f7365d49428df" +checksum = "fd68e32f5bd94849131670d34e21b0fb66057e91bee8e451e7f4e216e71616d2" dependencies = [ "ibc-types-core-channel", "ibc-types-core-client", @@ -1372,9 +1374,9 @@ dependencies = [ [[package]] name = "ibc-types-core-channel" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1dcce2afa6b83fc6f6bd0d626d3f31aaf62a9e9087fcef24e0f705148915cb56" +checksum = "c57da64f7e945e7443035275cc279e0176c988ea625968c2526706aafcff1766" dependencies = [ "anyhow", "bytes", @@ -1405,9 +1407,9 @@ dependencies = [ [[package]] name = "ibc-types-core-client" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "99ea8df52f9218da5f8e7daed1b22ca6b01b64711950b1c72a493f2d11660b9f" +checksum = "108a89f64ffa39e04a7e29de9d9824523c3164f8518927716be870ad8a7aedf9" dependencies = [ "anyhow", "bytes", @@ -1432,9 +1434,9 @@ dependencies = [ [[package]] name = "ibc-types-core-commitment" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "4b3583a2b7bd4d7f0b75177b619e9b0e0c317ece069170a348675913ad9a8125" +checksum = "8949e33fbb9c3f1ae49588f4829977e80439f3dfc1491b5a69492f5e4a036949" dependencies = [ "anyhow", "bytes", @@ -1467,9 +1469,9 @@ dependencies = [ [[package]] name = "ibc-types-core-connection" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "5a989af1209864891c95645585fb2048720087968ae419288949965aa65fc4b5" +checksum = "f63035288d11e5830daf20fd99507a671ab1cc03fa04752d75ef1c75e2786543" dependencies = [ "anyhow", "bytes", @@ -1497,9 +1499,9 @@ dependencies = [ [[package]] name = "ibc-types-domain-type" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fabf22b6da00e7d41dd50e8f3009fc112be5f8c9cdc131d3e37ed264844f5131" +checksum = "c2c543a23a77a5d814e0aeffd5a918964468a8b213f664897bc3f8c50cc7d5c1" dependencies = [ "anyhow", "bytes", @@ -1508,9 +1510,9 @@ dependencies = [ [[package]] name = "ibc-types-identifier" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "bd1070c50d4f031474472d404a77847a32233396cd8397b1145cfd555f88573d" +checksum = "b2ed2d7f06055bb2548564bf02c8c4b47561134f282adae61dc5c6ed722e1cde" dependencies = [ "displaydoc", "serde", @@ -1519,9 +1521,9 @@ dependencies = [ [[package]] name = "ibc-types-lightclients-tendermint" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "50390dbcbcb4d6f34a9ab4a1823196813c6171a9a7c28f0c6f498162c3d3aa0b" +checksum = "00fd846b7eedca1dfbb7babeb55e0d74fb0d09a7db63da93737d54a581a96371" dependencies = [ "anyhow", "bytes", @@ -1556,9 +1558,9 @@ dependencies = [ [[package]] name = "ibc-types-path" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3088ab0bd2a33ccd4fb522497a65a23a540be699f63342ff3c22268708a08271" +checksum = "0f4da80e010427aff50f227920cdc8f7879469ce2db181cbcf92dc228172a334" dependencies = [ "bytes", "derive_more", @@ -1579,9 +1581,9 @@ dependencies = [ [[package]] name = "ibc-types-timestamp" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a108c721a477aaf2f3fd8c392577d6c71f03b5c54c8cd09d58365ab7aa16182b" +checksum = "748b2fbebe30ca799d31f6fc220d705b5180a608db842e29a3722671411d81a6" dependencies = [ "bytes", "displaydoc", @@ -1598,9 +1600,9 @@ dependencies = [ [[package]] name = "ibc-types-transfer" -version = "0.12.1" +version = "0.15.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0f7198d1f63d8428a96a60b2534dbf2bba5594d36745db6166538ef9d89c3fef" +checksum = "8fcb46fe34a87db49bc9bb20918d7f882871a20d741d866d217c52a5dbe16a72" dependencies = [ "displaydoc", "serde", @@ -1621,9 +1623,9 @@ dependencies = [ [[package]] name = "ics23" -version = "0.11.3" +version = "0.12.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18798160736c1e368938ba6967dbcb3c7afb3256b442a5506ba5222eebb68a5a" +checksum = "73b17f1a5bd7d12ad30a21445cfa5f52fd7651cb3243ba866f9916b1ec112f12" dependencies = [ "anyhow", "blake2", @@ -1688,12 +1690,6 @@ dependencies = [ "syn 1.0.109", ] -[[package]] -name = "indenter" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ce23b50ad8242c51a442f3ff322d56b02f08852c77e4c0b4d3fd684abc89c683" - [[package]] name = "indexed_db_futures" version = "0.5.0" @@ -1763,18 +1759,9 @@ dependencies = [ [[package]] name = "itertools" -version = "0.11.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b1c173a5686ce8bfa551b3563d0c2170bf24ca44da99c7ca4bfdab5418c3fe57" -dependencies = [ - "either", -] - -[[package]] -name = "itertools" -version = "0.12.1" +version = "0.13.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "ba291022dbbd398a455acf126c1e341954079855bc60dfdda641363bd6922569" +checksum = "413ee7dfc52ee1a4949ceeb7dbc8a33f2d6c088194d9f922fb8318faf1f01186" dependencies = [ "either", ] @@ -1898,9 +1885,9 @@ checksum = "78ca9ab1a0babb1e7d5695e3530886289c18cf2f87ec19a575a0abdce112e3a3" [[package]] name = "metrics" -version = "0.22.3" +version = "0.24.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2be3cbd384d4e955b231c895ce10685e3d8260c5ccffae898c96c723b0772835" +checksum = "7a7deb012b3b2767169ff203fadb4c6b0b82b947512e5eb9e0b78c2e186ad9e3" dependencies = [ "ahash", "portable-atomic", @@ -1947,17 +1934,6 @@ version = "0.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "51d515d32fb182ee37cda2ccdcb92950d6a3c2893aa280e540671c2cd0f3b1d9" -[[package]] -name = "num-derive" -version = "0.3.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "876a53fff98e03a936a674b29568b0e605f06b29372c2489ff4de23f1949743d" -dependencies = [ - "proc-macro2 1.0.86", - "quote", - "syn 1.0.109", -] - [[package]] name = "num-integer" version = "0.1.46" @@ -2057,9 +2033,9 @@ checksum = "57c0d7b74b563b49d38dae00a0c37d4d6de9b432382b2892f0574ddcae73fd0a" [[package]] name = "pbjson" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1030c719b0ec2a2d25a5df729d6cff1acf3cc230bf766f4f97833591f7577b90" +checksum = "c7e6349fa080353f4a597daffd05cb81572a9c031a6d4fff7e504947496fcc68" dependencies = [ "base64 0.21.7", "serde", @@ -2067,21 +2043,21 @@ dependencies = [ [[package]] name = "pbjson-build" -version = "0.6.2" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2580e33f2292d34be285c5bc3dba5259542b083cfad6037b6d70345f24dcb735" +checksum = "6eea3058763d6e656105d1403cb04e0a41b7bbac6362d413e7c33be0c32279c9" dependencies = [ - "heck 0.4.1", - "itertools 0.11.0", + "heck 0.5.0", + "itertools 0.13.0", "prost", "prost-types", ] [[package]] name = "pbjson-types" -version = "0.6.0" +version = "0.7.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "18f596653ba4ac51bdecbb4ef6773bc7f56042dc13927910de1684ad3d32aa12" +checksum = "e54e5e7bfb1652f95bc361d76f3c780d8e526b134b85417e774166ee941f0887" dependencies = [ "bytes", "chrono", @@ -2103,9 +2079,9 @@ dependencies = [ ] [[package]] -name = "penumbra-asset" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-asset" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2128,8 +2104,8 @@ dependencies = [ "num-bigint", "once_cell", "pbjson-types", - "penumbra-num", - "penumbra-proto", + "penumbra-sdk-num", + "penumbra-sdk-proto", "poseidon377", "rand", "rand_core", @@ -2142,9 +2118,9 @@ dependencies = [ ] [[package]] -name = "penumbra-auction" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-auction" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2163,16 +2139,16 @@ dependencies = [ "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", + "penumbra-sdk-asset", + "penumbra-sdk-dex", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "prost", "prost-types", "rand_chacha", @@ -2187,9 +2163,9 @@ dependencies = [ ] [[package]] -name = "penumbra-community-pool" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-community-pool" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2201,13 +2177,13 @@ dependencies = [ "metrics", "once_cell", "pbjson-types", - "penumbra-asset", - "penumbra-keys", - "penumbra-num", - "penumbra-proto", - "penumbra-sct", - "penumbra-shielded-pool", - "penumbra-txhash", + "penumbra-sdk-asset", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-txhash", "prost", "serde", "sha2 0.10.8", @@ -2217,9 +2193,9 @@ dependencies = [ ] [[package]] -name = "penumbra-compact-block" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-compact-block" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2230,16 +2206,16 @@ dependencies = [ "futures", "im", "metrics", - "penumbra-dex", - "penumbra-fee", - "penumbra-governance", - "penumbra-ibc", - "penumbra-proof-params", - "penumbra-proto", - "penumbra-sct", - "penumbra-shielded-pool", - "penumbra-stake", - "penumbra-tct", + "penumbra-sdk-dex", + "penumbra-sdk-fee", + "penumbra-sdk-governance", + "penumbra-sdk-ibc", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-stake", + "penumbra-sdk-tct", "rand", "rand_core", "serde", @@ -2248,9 +2224,9 @@ dependencies = [ ] [[package]] -name = "penumbra-dex" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-dex" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2275,16 +2251,16 @@ dependencies = [ "once_cell", "parking_lot", "pbjson-types", - "penumbra-asset", - "penumbra-fee", - "penumbra-keys", - "penumbra-num", - "penumbra-proof-params", - "penumbra-proto", - "penumbra-sct", - "penumbra-shielded-pool", - "penumbra-tct", - "penumbra-txhash", + "penumbra-sdk-asset", + "penumbra-sdk-fee", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "poseidon377", "prost", "rand_core", @@ -2301,25 +2277,25 @@ dependencies = [ ] [[package]] -name = "penumbra-distributions" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-distributions" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "async-trait", - "penumbra-asset", - "penumbra-num", - "penumbra-proto", - "penumbra-sct", + "penumbra-sdk-asset", + "penumbra-sdk-num", + "penumbra-sdk-proto", + "penumbra-sdk-sct", "serde", "tendermint", "tracing", ] [[package]] -name = "penumbra-fee" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-fee" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2330,9 +2306,9 @@ dependencies = [ "decaf377-rdsa", "im", "metrics", - "penumbra-asset", - "penumbra-num", - "penumbra-proto", + "penumbra-sdk-asset", + "penumbra-sdk-num", + "penumbra-sdk-proto", "rand", "rand_core", "serde", @@ -2341,9 +2317,40 @@ dependencies = [ ] [[package]] -name = "penumbra-governance" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-funding" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" +dependencies = [ + "anyhow", + "ark-groth16", + "async-trait", + "base64 0.21.7", + "decaf377", + "decaf377-rdsa", + "penumbra-sdk-asset", + "penumbra-sdk-community-pool", + "penumbra-sdk-dex", + "penumbra-sdk-distributions", + "penumbra-sdk-governance", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-stake", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", + "rand", + "serde", + "tendermint", + "tracing", +] + +[[package]] +name = "penumbra-sdk-governance" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2365,18 +2372,18 @@ dependencies = [ "metrics", "once_cell", "pbjson-types", - "penumbra-asset", - "penumbra-distributions", - "penumbra-ibc", - "penumbra-keys", - "penumbra-num", - "penumbra-proof-params", - "penumbra-proto", - "penumbra-sct", - "penumbra-shielded-pool", - "penumbra-stake", - "penumbra-tct", - "penumbra-txhash", + "penumbra-sdk-asset", + "penumbra-sdk-distributions", + "penumbra-sdk-ibc", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-stake", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "rand", "rand_chacha", "rand_core", @@ -2390,9 +2397,9 @@ dependencies = [ ] [[package]] -name = "penumbra-ibc" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-ibc" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2408,11 +2415,11 @@ dependencies = [ "num-traits", "once_cell", "pbjson-types", - "penumbra-asset", - "penumbra-num", - "penumbra-proto", - "penumbra-sct", - "penumbra-txhash", + "penumbra-sdk-asset", + "penumbra-sdk-num", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-txhash", "prost", "serde", "serde_json", @@ -2425,9 +2432,9 @@ dependencies = [ ] [[package]] -name = "penumbra-keys" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-keys" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "aes", "anyhow", @@ -2455,9 +2462,9 @@ dependencies = [ "num-bigint", "once_cell", "pbkdf2", - "penumbra-asset", - "penumbra-proto", - "penumbra-tct", + "penumbra-sdk-asset", + "penumbra-sdk-proto", + "penumbra-sdk-tct", "poseidon377", "rand", "rand_core", @@ -2469,9 +2476,9 @@ dependencies = [ ] [[package]] -name = "penumbra-num" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-num" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2494,7 +2501,7 @@ dependencies = [ "ibig", "num-bigint", "once_cell", - "penumbra-proto", + "penumbra-sdk-proto", "rand", "rand_core", "regex", @@ -2505,9 +2512,9 @@ dependencies = [ ] [[package]] -name = "penumbra-proof-params" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-proof-params" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ec", @@ -2530,9 +2537,9 @@ dependencies = [ ] [[package]] -name = "penumbra-proto" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-proto" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "async-trait", @@ -2557,9 +2564,9 @@ dependencies = [ ] [[package]] -name = "penumbra-sct" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-sct" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2578,9 +2585,10 @@ dependencies = [ "metrics", "once_cell", "pbjson-types", - "penumbra-keys", - "penumbra-proto", - "penumbra-tct", + "penumbra-sdk-keys", + "penumbra-sdk-proto", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "poseidon377", "rand", "rand_core", @@ -2590,9 +2598,9 @@ dependencies = [ ] [[package]] -name = "penumbra-shielded-pool" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-shielded-pool" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2618,15 +2626,15 @@ dependencies = [ "im", "metrics", "once_cell", - "penumbra-asset", - "penumbra-ibc", - "penumbra-keys", - "penumbra-num", - "penumbra-proof-params", - "penumbra-proto", - "penumbra-sct", - "penumbra-tct", - "penumbra-txhash", + "penumbra-sdk-asset", + "penumbra-sdk-ibc", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "poseidon377", "prost", "rand", @@ -2641,9 +2649,9 @@ dependencies = [ ] [[package]] -name = "penumbra-stake" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-stake" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2659,16 +2667,16 @@ dependencies = [ "decaf377-rdsa", "hex", "once_cell", - "penumbra-asset", - "penumbra-distributions", - "penumbra-keys", - "penumbra-num", - "penumbra-proof-params", - "penumbra-proto", - "penumbra-sct", - "penumbra-shielded-pool", - "penumbra-tct", - "penumbra-txhash", + "penumbra-sdk-asset", + "penumbra-sdk-distributions", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "rand_chacha", "rand_core", "regex", @@ -2682,9 +2690,9 @@ dependencies = [ ] [[package]] -name = "penumbra-tct" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-tct" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "ark-ed-on-bls12-377", "ark-ff", @@ -2702,7 +2710,7 @@ dependencies = [ "im", "once_cell", "parking_lot", - "penumbra-proto", + "penumbra-sdk-proto", "poseidon377", "rand", "serde", @@ -2711,9 +2719,9 @@ dependencies = [ ] [[package]] -name = "penumbra-transaction" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-transaction" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "ark-ff", @@ -2734,22 +2742,23 @@ dependencies = [ "num-bigint", "once_cell", "pbjson-types", - "penumbra-asset", - "penumbra-auction", - "penumbra-community-pool", - "penumbra-dex", - "penumbra-fee", - "penumbra-governance", - "penumbra-ibc", - "penumbra-keys", - "penumbra-num", - "penumbra-proof-params", - "penumbra-proto", - "penumbra-sct", - "penumbra-shielded-pool", - "penumbra-stake", - "penumbra-tct", - "penumbra-txhash", + "penumbra-sdk-asset", + "penumbra-sdk-auction", + "penumbra-sdk-community-pool", + "penumbra-sdk-dex", + "penumbra-sdk-fee", + "penumbra-sdk-funding", + "penumbra-sdk-governance", + "penumbra-sdk-ibc", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-stake", + "penumbra-sdk-tct", + "penumbra-sdk-txhash", "poseidon377", "rand", "rand_core", @@ -2762,16 +2771,16 @@ dependencies = [ ] [[package]] -name = "penumbra-txhash" -version = "0.81.3" -source = "git+https://github.com/penumbra-zone/penumbra.git?tag=v0.81.3#8521a130b43aca39892a5ba27eb95eddc9a263ed" +name = "penumbra-sdk-txhash" +version = "2.0.0-alpha.2" +source = "git+https://github.com/penumbra-zone/penumbra.git?rev=ae2300bce202a7d429727f1340e7412d3b9f810c#ae2300bce202a7d429727f1340e7412d3b9f810c" dependencies = [ "anyhow", "blake2b_simd 1.0.2", "getrandom", "hex", - "penumbra-proto", - "penumbra-tct", + "penumbra-sdk-proto", + "penumbra-sdk-tct", "serde", ] @@ -2786,21 +2795,22 @@ dependencies = [ "decaf377", "hex", "indexed_db_futures", - "penumbra-asset", - "penumbra-auction", - "penumbra-compact-block", - "penumbra-dex", - "penumbra-fee", - "penumbra-governance", - "penumbra-keys", - "penumbra-num", - "penumbra-proof-params", - "penumbra-proto", - "penumbra-sct", - "penumbra-shielded-pool", - "penumbra-stake", - "penumbra-tct", - "penumbra-transaction", + "penumbra-sdk-asset", + "penumbra-sdk-auction", + "penumbra-sdk-compact-block", + "penumbra-sdk-dex", + "penumbra-sdk-fee", + "penumbra-sdk-funding", + "penumbra-sdk-governance", + "penumbra-sdk-keys", + "penumbra-sdk-num", + "penumbra-sdk-proof-params", + "penumbra-sdk-proto", + "penumbra-sdk-sct", + "penumbra-sdk-shielded-pool", + "penumbra-sdk-stake", + "penumbra-sdk-tct", + "penumbra-sdk-transaction", "prost", "rand_core", "regex", @@ -2991,9 +3001,9 @@ dependencies = [ [[package]] name = "prost" -version = "0.12.6" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "deb1435c188b76130da55f17a466d252ff7b1418b2ad3e037d127b94e3411f29" +checksum = "2c0fef6c4230e4ccf618a35c59d7ede15dea37de8427500f50aff708806e42ec" dependencies = [ "bytes", "prost-derive", @@ -3001,13 +3011,12 @@ dependencies = [ [[package]] name = "prost-build" -version = "0.12.6" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "22505a5c94da8e3b7c2996394d1c933236c4d743e81a410bcca4e6989fc066a4" +checksum = "d0f3e5beed80eb580c68e2c600937ac2c4eedabdfd5ef1e5b7ea4f3fba84497b" dependencies = [ - "bytes", - "heck 0.5.0", - "itertools 0.12.1", + "heck 0.4.1", + "itertools 0.10.5", "log", "multimap", "once_cell", @@ -3022,12 +3031,12 @@ dependencies = [ [[package]] name = "prost-derive" -version = "0.12.6" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "81bddcdb20abf9501610992b6759a4c888aef7d1a7247ef75e2404275ac24af1" +checksum = "157c5a9d7ea5c2ed2d9fb8f495b64759f7816c7eaea54ba3978f0d63000162e3" dependencies = [ "anyhow", - "itertools 0.12.1", + "itertools 0.10.5", "proc-macro2 1.0.86", "quote", "syn 2.0.79", @@ -3035,9 +3044,9 @@ dependencies = [ [[package]] name = "prost-types" -version = "0.12.6" +version = "0.13.4" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9091c90b0a32608e984ff2fa4091273cbdd755d54935c51d520887f4a1dbd5b0" +checksum = "cc2f1e56baa61e93533aebc21af4d2134b70f66275e0fcdf3cbe43d77ff7e8fc" dependencies = [ "prost", ] @@ -3573,9 +3582,9 @@ dependencies = [ [[package]] name = "tendermint" -version = "0.34.1" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "15ab8f0a25d0d2ad49ac615da054d6a76aa6603ff95f7d18bafdd34450a1a04b" +checksum = "d9703e34d940c2a293804752555107f8dbe2b84ec4c6dd5203831235868105d2" dependencies = [ "bytes", "digest 0.10.7", @@ -3586,7 +3595,6 @@ dependencies = [ "num-traits", "once_cell", "prost", - "prost-types", "serde", "serde_bytes", "serde_json", @@ -3602,9 +3610,9 @@ dependencies = [ [[package]] name = "tendermint-light-client-verifier" -version = "0.34.1" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9b8090d0eef9ad57b1b913b5e358e26145c86017e87338136509b94383a4af25" +checksum = "f0cda4a449fc70985a95f892a67286f13afa4e048d90b8d04a2bf6341e88d1c2" dependencies = [ "derive_more", "flex-error", @@ -3615,16 +3623,13 @@ dependencies = [ [[package]] name = "tendermint-proto" -version = "0.34.1" +version = "0.40.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b797dd3d2beaaee91d2f065e7bdf239dc8d80bba4a183a288bc1279dd5a69a1e" +checksum = "9ae9e1705aa0fa5ecb2c6aa7fb78c2313c4a31158ea5f02048bf318f849352eb" dependencies = [ "bytes", "flex-error", - "num-derive", - "num-traits", "prost", - "prost-types", "serde", "serde_bytes", "subtle-encoding", diff --git a/packages/wasm/crate/Cargo.toml b/packages/wasm/crate/Cargo.toml index 5ef062af72..b29c4d077c 100644 --- a/packages/wasm/crate/Cargo.toml +++ b/packages/wasm/crate/Cargo.toml @@ -14,22 +14,23 @@ default = ["console_error_panic_hook"] mock-database = [] [dependencies] -# Update to tag dependency once https://github.com/penumbra-zone/penumbra/pull/4878 is in a release -penumbra-auction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-auction", default-features = false } -penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-asset" } -penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-compact-block", default-features = false } -penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-dex", default-features = false } -penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-fee", default-features = false } -penumbra-governance = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-governance", default-features = false } -penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-keys" } -penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-num" } -penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-proof-params", default-features = false } -penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-proto", default-features = false } -penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-sct", default-features = false } -penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-shielded-pool", default-features = false } -penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-stake", default-features = false } -penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-tct" } -penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", tag = "v0.81.3", package = "penumbra-transaction", default-features = false } +# Update to tag dependency once https://github.com/penumbra-zone/penumbra/tree/protocol/lqt_branch is in a release +penumbra-auction = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-auction", default-features = false } +penumbra-asset = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-asset" } +penumbra-compact-block = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-compact-block", default-features = false } +penumbra-dex = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-dex", default-features = false } +penumbra-fee = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-fee", default-features = false } +penumbra-governance = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-governance", default-features = false } +penumbra-keys = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-keys" } +penumbra-num = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-num" } +penumbra-proof-params = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-proof-params", default-features = false } +penumbra-proto = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-proto", default-features = false } +penumbra-sct = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-sct", default-features = false } +penumbra-shielded-pool = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-shielded-pool", default-features = false } +penumbra-stake = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-stake", default-features = false } +penumbra-tct = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-tct" } +penumbra-transaction = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-transaction", default-features = false } +penumbra-funding = { git = "https://github.com/penumbra-zone/penumbra.git", rev = "ae2300bce202a7d429727f1340e7412d3b9f810c", package = "penumbra-sdk-funding", default-features = false } anyhow = "1.0.89" ark-ff = { version = "0.4.2", features = ["std"] } @@ -38,7 +39,7 @@ console_error_panic_hook = { version = "0.1.7", optional = true } decaf377 = { version = "0.10.1", features = ["r1cs"] } hex = "0.4.3" indexed_db_futures = "0.5.0" -prost = "0.12.6" +prost = "0.13.4" rand_core = { version = "0.6.4", features = ["getrandom"] } regex = { version = "1.11.0" } serde = { version = "1.0.210", features = ["derive"] } diff --git a/packages/wasm/crate/src/lib.rs b/packages/wasm/crate/src/lib.rs index f060831b71..4cc5a3941e 100644 --- a/packages/wasm/crate/src/lib.rs +++ b/packages/wasm/crate/src/lib.rs @@ -15,3 +15,4 @@ pub mod tree; pub mod tx; pub mod utils; pub mod view_server; +pub mod voting; diff --git a/packages/wasm/crate/src/planner.rs b/packages/wasm/crate/src/planner.rs index 92f0c8d4c7..2cbc51429f 100644 --- a/packages/wasm/crate/src/planner.rs +++ b/packages/wasm/crate/src/planner.rs @@ -3,7 +3,7 @@ use std::mem; use anyhow::anyhow; use decaf377::{Fq, Fr}; -use penumbra_asset::asset::{Id, Metadata}; +use penumbra_asset::asset::{Denom, Id, Metadata}; use penumbra_asset::Value; use penumbra_auction::auction::dutch::actions::ActionDutchAuctionWithdrawPlan; use penumbra_auction::auction::dutch::{ @@ -17,25 +17,26 @@ use penumbra_dex::{ PositionClose, PositionOpen, TradingPair, }; use penumbra_fee::FeeTier; +use penumbra_funding::liquidity_tournament::ActionLiquidityTournamentVotePlan; use penumbra_governance::DelegatorVotePlan; use penumbra_keys::keys::AddressIndex; -use penumbra_keys::FullViewingKey; +use penumbra_keys::{Address, FullViewingKey}; use penumbra_num::Amount; use penumbra_proto::core::app::v1::AppParameters; use penumbra_proto::core::component::ibc; use penumbra_proto::view::v1::{ transaction_planner_request as tpr, NotesRequest, TransactionPlannerRequest, }; -use penumbra_proto::DomainType; +use penumbra_proto::{DomainType, Message}; use penumbra_sct::params::SctParameters; -use penumbra_shielded_pool::{fmd, OutputPlan, SpendPlan}; +use penumbra_shielded_pool::{fmd, Note, OutputPlan, SpendPlan}; use penumbra_stake::rate::RateData; use penumbra_stake::{IdentityKey, Penalty, Undelegate, UndelegateClaimPlan}; +use penumbra_tct::Position; use penumbra_transaction::gas::swap_claim_gas_cost; use penumbra_transaction::memo::MemoPlaintext; use penumbra_transaction::{plan::MemoPlan, ActionPlan, TransactionParameters}; use penumbra_transaction::{ActionList, TransactionPlan}; -use prost::Message; use rand_core::{OsRng, RngCore}; use wasm_bindgen::prelude::wasm_bindgen; use wasm_bindgen::JsValue; @@ -169,7 +170,8 @@ pub async fn plan_transaction( ) -> WasmResult { utils::set_panic_hook(); - let tx_planner_req = TransactionPlannerRequest::decode(request)?; + let tx_planner_req = TransactionPlannerRequest::decode(request) + .expect("transaction planner request is malformed"); let fvk: FullViewingKey = FullViewingKey::decode(full_viewing_key)?; let fee_asset_id = Id::decode(gas_fee_token)?; let constants: DbConstants = serde_wasm_bindgen::from_value(idb_constants)?; @@ -563,7 +565,6 @@ pub async fn plan_transaction_inner( validator_start_rate_data.unbonded_amount(record.note.amount()); let domain_vote = vote - .clone() .map(TryInto::try_into) .transpose()? .ok_or_else(|| anyhow::anyhow!("missing vote param"))?; @@ -655,6 +656,51 @@ pub async fn plan_transaction_inner( change_address = address.try_into()?; } + for tpr::ActionLiquidityTournamentVote { + incentivized, + rewards_recipient, + staked_notes, + epoch_index, + } in request.action_liquidity_tournament_vote + { + if staked_notes.is_empty() { + let error_message = + "Invalid transaction: zero delegation notes for voting in the liquidity tournament" + .to_string(); + return Err(WasmError::Anyhow(anyhow!(error_message))); + } + + for staked_note in staked_notes { + let incentivized: Denom = incentivized + .clone() + .ok_or_else(|| anyhow!("missing incentivized asset in liquidity tournament"))? + .try_into()?; + let rewards_recipient: Address = rewards_recipient + .clone() + .ok_or_else(|| anyhow!("missing rewards recipient in liquidity tournament"))? + .try_into()?; + let note: Note = staked_note + .note + .ok_or_else(|| anyhow!("missing note in liquidity tournament"))? + .try_into()?; + let staked_note_position: Position = staked_note.position.into(); + let start_position: Position = epoch_index.into(); + + actions_list.push(ActionPlan::ActionLiquidityTournamentVote( + ActionLiquidityTournamentVotePlan { + incentivized, + rewards_recipient, + staked_note: note, + staked_note_position, + start_position, + randomizer: Fr::rand(&mut OsRng), + proof_blinding_r: Fq::rand(&mut OsRng), + proof_blinding_s: Fq::rand(&mut OsRng), + }, + )); + } + } + // Phase 2: balance the transaction with information from the view service. // // It's possible that adding spends could increase the gas, increasing diff --git a/packages/wasm/crate/src/voting.rs b/packages/wasm/crate/src/voting.rs new file mode 100644 index 0000000000..f727ac92bf --- /dev/null +++ b/packages/wasm/crate/src/voting.rs @@ -0,0 +1,39 @@ +use crate::error::WasmResult; +use crate::storage::{init_idb_storage, DbConstants}; +use crate::utils; +use penumbra_keys::keys::AddressIndex; +use penumbra_proto::DomainType; +use wasm_bindgen::prelude::wasm_bindgen; +use wasm_bindgen::JsValue; + +/// Utility for requesting voting notes. +#[wasm_bindgen] +pub async fn get_voting_notes( + address_index: &[u8], + votable_at_height: u64, + idb_constants: JsValue, +) -> WasmResult { + utils::set_panic_hook(); + + let constants: DbConstants = serde_wasm_bindgen::from_value(idb_constants) + .expect("failed to convert idb_constants from JsValue to DbConstants"); + let storage = init_idb_storage(constants) + .await + .expect("failed to initialize IndexedDB storage"); + + let address_index: AddressIndex = + AddressIndex::decode(address_index).expect("failed to decode AddressIndex from byte slice"); + let proto_address_index = penumbra_proto::core::keys::v1::AddressIndex::from(address_index); + + let voting_notes: Vec<( + crate::note_record::SpendableNoteRecord, + penumbra_stake::IdentityKey, + )> = storage + .get_notes_for_voting(Some(proto_address_index), votable_at_height) + .await + .expect("failed to retrieve voting notes from storage"); + + let result = serde_wasm_bindgen::to_value(&voting_notes) + .expect("failed to convert voting notes to JsValue"); + Ok(result) +} diff --git a/packages/wasm/crate/tests/test_build.rs b/packages/wasm/crate/tests/test_build.rs index 649fb57f73..e43a05a42c 100644 --- a/packages/wasm/crate/tests/test_build.rs +++ b/packages/wasm/crate/tests/test_build.rs @@ -257,6 +257,7 @@ async fn mock_build_serial_and_parallel() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], }; // Viewing key to reveal asset balances and transactions. diff --git a/packages/wasm/crate/tests/test_liquidity_tournament_voting.rs b/packages/wasm/crate/tests/test_liquidity_tournament_voting.rs new file mode 100644 index 0000000000..3ded2446b4 --- /dev/null +++ b/packages/wasm/crate/tests/test_liquidity_tournament_voting.rs @@ -0,0 +1,362 @@ +use crate::utils::planner_setup::seed_params_in_db; +use penumbra_asset::asset::Metadata; +use penumbra_asset::{Value, STAKING_TOKEN_ASSET_ID}; +use penumbra_keys::Address; +use penumbra_keys::FullViewingKey; +use penumbra_proto::core::asset::v1 as pb; +use penumbra_proto::core::keys::v1::Address as AddressProto; +use penumbra_proto::view::v1::transaction_planner_request::ActionLiquidityTournamentVote; +use penumbra_proto::view::v1::{ + SpendableNoteRecord as SpendableNoteRecordProto, TransactionPlannerRequest, +}; +use penumbra_sct::{CommitmentSource, Nullifier}; +use penumbra_shielded_pool::Note; +use penumbra_tct::StateCommitment; +use penumbra_wasm::database::interface::Database; +use penumbra_wasm::database::mock::{get_mock_tables, MockDb}; +use penumbra_wasm::error::WasmError; +use penumbra_wasm::note_record::SpendableNoteRecord; +use penumbra_wasm::planner::plan_transaction_inner; +use penumbra_wasm::storage::{Storage, Tables}; +use rand_core::OsRng; +use std::str::FromStr; +use wasm_bindgen_test::wasm_bindgen_test; +mod utils; +wasm_bindgen_test::wasm_bindgen_test_configure!(run_in_browser); + +async fn setup_env(mock_db: &MockDb, tables: &Tables) { + seed_params_in_db(mock_db, tables).await; + + let fee_note = SpendableNoteRecord { + note_commitment: StateCommitment::try_from([0; 32]).unwrap(), + note: Note::generate( + &mut OsRng, + &Address::dummy(&mut OsRng), + Value { + amount: 1558828u64.into(), + asset_id: *STAKING_TOKEN_ASSET_ID, + }, + ), + address_index: Default::default(), + nullifier: Nullifier::try_from(vec![ + 76, 12, 37, 160, 207, 93, 129, 238, 230, 254, 29, 227, 107, 97, 138, 12, 172, 130, 138, + 66, 123, 217, 253, 148, 178, 91, 112, 125, 247, 32, 189, 2, + ]) + .unwrap(), + height_created: 0, + height_spent: None, + position: Default::default(), + source: CommitmentSource::Genesis, + return_address: None, + }; + + mock_db + .put_with_key(&tables.spendable_notes, "fee_note", &fee_note) + .await + .unwrap(); +} + +#[wasm_bindgen_test] +async fn test_liquidity_voting_no_delegation_notes() { + let mock_db = MockDb::new(); + let tables = get_mock_tables(); + + setup_env(&mock_db, &tables).await; + + let storage = Storage::new(mock_db, tables).unwrap(); + + let notes = storage.get_notes_for_voting(None, 42).await.unwrap(); + let spendable_notes_proto: Vec = + notes.into_iter().map(|(note, _)| note.into()).collect(); + + let denom = pb::Denom { + denom: + "udelegation_penumbravalid1hz2hqlgx4w55vkxzv0n3u93czlkvm6zpgftyny2psg3dp8vcygxqd7fedt" + .to_string(), + }; + + let rewards_recipient: AddressProto = Address::dummy(&mut OsRng).into(); + + #[allow(deprecated)] + let req = TransactionPlannerRequest { + expiry_height: 0, + memo: None, + source: None, + outputs: vec![], + spends: vec![], + swaps: vec![], + swap_claims: vec![], + delegations: vec![], + undelegations: vec![], + undelegation_claims: vec![], + ibc_relay_actions: vec![], + ics20_withdrawals: vec![], + position_opens: vec![], + position_closes: vec![], + position_withdraws: vec![], + dutch_auction_schedule_actions: vec![], + dutch_auction_end_actions: vec![], + dutch_auction_withdraw_actions: vec![], + delegator_votes: vec![], + action_liquidity_tournament_vote: vec![ActionLiquidityTournamentVote { + incentivized: Some(denom), + rewards_recipient: Some(rewards_recipient), + staked_notes: spendable_notes_proto, + epoch_index: 1, + }], + epoch_index: 1, + epoch: None, + fee_mode: None, + }; + let full_viewing_key = FullViewingKey::from_str("penumbrafullviewingkey1mnm04x7yx5tyznswlp0sxs8nsxtgxr9p98dp0msuek8fzxuknuzawjpct8zdevcvm3tsph0wvsuw33x2q42e7sf29q904hwerma8xzgrxsgq2").unwrap(); + + let fee_id = *STAKING_TOKEN_ASSET_ID; + + let res = plan_transaction_inner(storage, req, full_viewing_key, fee_id).await; + assert!(matches!( + res, + Err(WasmError::Anyhow(e)) if e.to_string() == *"Invalid transaction: zero delegation notes for voting in the liquidity tournament" + )); +} + +#[wasm_bindgen_test] +async fn test_liquidity_voting_single_delegation_note() { + let mock_db = MockDb::new(); + let tables = get_mock_tables(); + + setup_env(&mock_db, &tables).await; + + let metadata_in_db_proto = pb::Metadata { + base: + "udelegation_penumbravalid1hz2hqlgx4w55vkxzv0n3u93czlkvm6zpgftyny2psg3dp8vcygxqd7fedt" + .to_string(), + ..Default::default() + }; + let metadata_in_db: Metadata = metadata_in_db_proto.clone().try_into().unwrap(); + mock_db + .put_with_key(&tables.assets, "metadata_a", &metadata_in_db_proto) + .await + .unwrap(); + + let eligible_a = SpendableNoteRecord { + note_commitment: StateCommitment::try_from([0; 32]).unwrap(), + note: Note::generate( + &mut OsRng, + &Address::dummy(&mut OsRng), + Value { + amount: 50u64.into(), + asset_id: metadata_in_db.id(), + }, + ), + address_index: Default::default(), + nullifier: Nullifier::try_from(vec![ + 76, 12, 37, 160, 207, 93, 129, 238, 230, 254, 29, 227, 107, 97, 138, 12, 172, 130, 138, + 66, 123, 217, 253, 148, 178, 91, 112, 125, 247, 32, 189, 2, + ]) + .unwrap(), + height_created: 0, + height_spent: None, + position: Default::default(), + source: CommitmentSource::Genesis, + return_address: None, + }; + + mock_db + .put_with_key(&tables.spendable_notes, "eligible_a", &eligible_a) + .await + .unwrap(); + + let storage = Storage::new(mock_db, tables).unwrap(); + + let notes = storage.get_notes_for_voting(None, 42).await.unwrap(); + let spendable_notes: Vec = + notes.clone().into_iter().map(|(note, _)| note).collect(); + let spendable_notes_proto: Vec = + notes.into_iter().map(|(note, _)| note.into()).collect(); + + assert_eq!(spendable_notes.len(), 1); + + let denom = pb::Denom { + denom: + "udelegation_penumbravalid1hz2hqlgx4w55vkxzv0n3u93czlkvm6zpgftyny2psg3dp8vcygxqd7fedt" + .to_string(), + }; + + let rewards_recipient: AddressProto = Address::dummy(&mut OsRng).into(); + + #[allow(deprecated)] + let req = TransactionPlannerRequest { + expiry_height: 0, + memo: None, + source: None, + outputs: vec![], + spends: vec![], + swaps: vec![], + swap_claims: vec![], + delegations: vec![], + undelegations: vec![], + undelegation_claims: vec![], + ibc_relay_actions: vec![], + ics20_withdrawals: vec![], + position_opens: vec![], + position_closes: vec![], + position_withdraws: vec![], + dutch_auction_schedule_actions: vec![], + dutch_auction_end_actions: vec![], + dutch_auction_withdraw_actions: vec![], + delegator_votes: vec![], + action_liquidity_tournament_vote: vec![ActionLiquidityTournamentVote { + incentivized: Some(denom), + rewards_recipient: Some(rewards_recipient), + staked_notes: spendable_notes_proto, + epoch_index: 1, + }], + epoch_index: 1, + epoch: None, + fee_mode: None, + }; + let full_viewing_key = FullViewingKey::from_str("penumbrafullviewingkey1mnm04x7yx5tyznswlp0sxs8nsxtgxr9p98dp0msuek8fzxuknuzawjpct8zdevcvm3tsph0wvsuw33x2q42e7sf29q904hwerma8xzgrxsgq2").unwrap(); + + let fee_id = *STAKING_TOKEN_ASSET_ID; + + let res = plan_transaction_inner(storage, req, full_viewing_key, fee_id) + .await + .unwrap(); + + assert_eq!(res.actions.len(), 3); +} + +#[wasm_bindgen_test] +async fn test_liquidity_voting_multiple_delegation_note() { + let mock_db = MockDb::new(); + let tables = get_mock_tables(); + + setup_env(&mock_db, &tables).await; + + let metadata_in_db_proto = pb::Metadata { + base: + "udelegation_penumbravalid1hz2hqlgx4w55vkxzv0n3u93czlkvm6zpgftyny2psg3dp8vcygxqd7fedt" + .to_string(), + ..Default::default() + }; + let metadata_in_db: Metadata = metadata_in_db_proto.clone().try_into().unwrap(); + mock_db + .put_with_key(&tables.assets, "metadata_a", &metadata_in_db_proto) + .await + .unwrap(); + + let eligible_a = SpendableNoteRecord { + note_commitment: StateCommitment::try_from([0; 32]).unwrap(), + note: Note::generate( + &mut OsRng, + &Address::dummy(&mut OsRng), + Value { + amount: 50u64.into(), + asset_id: metadata_in_db.id(), + }, + ), + address_index: Default::default(), + nullifier: Nullifier::try_from(vec![ + 76, 12, 37, 160, 207, 93, 129, 238, 230, 254, 29, 227, 107, 97, 138, 12, 172, 130, 138, + 66, 123, 217, 253, 148, 178, 91, 112, 125, 247, 32, 189, 2, + ]) + .unwrap(), + height_created: 0, + height_spent: None, + position: Default::default(), + source: CommitmentSource::Genesis, + return_address: None, + }; + + mock_db + .put_with_key(&tables.spendable_notes, "eligible_a", &eligible_a) + .await + .unwrap(); + + let eligible_b = SpendableNoteRecord { + note_commitment: StateCommitment::try_from([0; 32]).unwrap(), + note: Note::generate( + &mut OsRng, + &Address::dummy(&mut OsRng), + Value { + amount: 50u64.into(), + asset_id: metadata_in_db.id(), + }, + ), + address_index: Default::default(), + nullifier: Nullifier::try_from(vec![ + 76, 12, 37, 160, 207, 93, 129, 238, 230, 254, 29, 227, 107, 97, 138, 12, 172, 130, 138, + 66, 123, 217, 253, 148, 178, 91, 112, 125, 247, 32, 189, 2, + ]) + .unwrap(), + height_created: 40, + height_spent: Some(42), + position: Default::default(), + source: CommitmentSource::Genesis, + return_address: None, + }; + + mock_db + .put_with_key(&tables.spendable_notes, "eligible_b", &eligible_b) + .await + .unwrap(); + + let storage = Storage::new(mock_db, tables).unwrap(); + + let notes = storage.get_notes_for_voting(None, 42).await.unwrap(); + let spendable_notes: Vec = + notes.clone().into_iter().map(|(note, _)| note).collect(); + let spendable_notes_proto: Vec = + notes.into_iter().map(|(note, _)| note.into()).collect(); + + assert_eq!(spendable_notes.len(), 2); + + let denom = pb::Denom { + denom: + "udelegation_penumbravalid1hz2hqlgx4w55vkxzv0n3u93czlkvm6zpgftyny2psg3dp8vcygxqd7fedt" + .to_string(), + }; + + let rewards_recipient: AddressProto = Address::dummy(&mut OsRng).into(); + + #[allow(deprecated)] + let req = TransactionPlannerRequest { + expiry_height: 0, + memo: None, + source: None, + outputs: vec![], + spends: vec![], + swaps: vec![], + swap_claims: vec![], + delegations: vec![], + undelegations: vec![], + undelegation_claims: vec![], + ibc_relay_actions: vec![], + ics20_withdrawals: vec![], + position_opens: vec![], + position_closes: vec![], + position_withdraws: vec![], + dutch_auction_schedule_actions: vec![], + dutch_auction_end_actions: vec![], + dutch_auction_withdraw_actions: vec![], + delegator_votes: vec![], + action_liquidity_tournament_vote: vec![ActionLiquidityTournamentVote { + incentivized: Some(denom), + rewards_recipient: Some(rewards_recipient), + staked_notes: spendable_notes_proto, + epoch_index: 1, + }], + epoch_index: 1, + epoch: None, + fee_mode: None, + }; + let full_viewing_key = FullViewingKey::from_str("penumbrafullviewingkey1mnm04x7yx5tyznswlp0sxs8nsxtgxr9p98dp0msuek8fzxuknuzawjpct8zdevcvm3tsph0wvsuw33x2q42e7sf29q904hwerma8xzgrxsgq2").unwrap(); + + let fee_id = *STAKING_TOKEN_ASSET_ID; + + let res = plan_transaction_inner(storage, req, full_viewing_key, fee_id) + .await + .unwrap(); + + assert_eq!(res.actions.len(), 4); +} diff --git a/packages/wasm/crate/tests/test_metadata.rs b/packages/wasm/crate/tests/test_metadata.rs index 0eafec0a11..e1b2f09e98 100644 --- a/packages/wasm/crate/tests/test_metadata.rs +++ b/packages/wasm/crate/tests/test_metadata.rs @@ -40,6 +40,7 @@ fn get_metadata_for(display_denom: &str, base_denom_is_display_denom: bool) -> p symbol: String::from(""), priority_score: 0, badges: Vec::new(), + coingecko_id: String::new(), } } diff --git a/packages/wasm/crate/tests/test_planner_delegator_vote.rs b/packages/wasm/crate/tests/test_planner_delegator_vote.rs index 7c1d981385..8ec19f7bd9 100644 --- a/packages/wasm/crate/tests/test_planner_delegator_vote.rs +++ b/packages/wasm/crate/tests/test_planner_delegator_vote.rs @@ -144,6 +144,7 @@ async fn test_delegator_votes_empty() { start_position: 0, rate_data: vec![], }], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -195,6 +196,7 @@ async fn test_no_rate_data_passed() { start_position: 0, rate_data: vec![], }], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -253,6 +255,7 @@ async fn test_rate_data_matches() { validator_exchange_rate: Some(Amount { lo: 12, hi: 8 }), }], }], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, diff --git a/packages/wasm/crate/tests/test_planner_general.rs b/packages/wasm/crate/tests/test_planner_general.rs index be458338ea..47061024d2 100644 --- a/packages/wasm/crate/tests/test_planner_general.rs +++ b/packages/wasm/crate/tests/test_planner_general.rs @@ -44,6 +44,7 @@ async fn test_planner_without_actions() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, diff --git a/packages/wasm/crate/tests/test_planner_send_max.rs b/packages/wasm/crate/tests/test_planner_send_max.rs index 00938afc89..a3dc9dba20 100644 --- a/packages/wasm/crate/tests/test_planner_send_max.rs +++ b/packages/wasm/crate/tests/test_planner_send_max.rs @@ -310,6 +310,7 @@ async fn test_multiple_action_plans_in_transaction_request() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -391,6 +392,7 @@ async fn test_multiple_spend_requests() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -462,6 +464,7 @@ async fn test_valid_spend_amount_validation_with_native_token() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -538,6 +541,7 @@ async fn test_invalid_spend_amount_validation_with_native_token() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -623,6 +627,7 @@ async fn test_spend_amount_validation_with_alternative_token() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -686,6 +691,7 @@ async fn test_spend_amount_validation_with_alternative_token() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -774,6 +780,7 @@ async fn test_invalid_fee_asset_id_validation() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -842,6 +849,7 @@ async fn test_filter_zero_value_notes() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, @@ -917,6 +925,7 @@ async fn test_empty_note_set() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], epoch_index: 0, epoch: None, fee_mode: None, diff --git a/packages/wasm/crate/tests/test_witness.rs b/packages/wasm/crate/tests/test_witness.rs index af130e2bb2..6d7bbb8def 100644 --- a/packages/wasm/crate/tests/test_witness.rs +++ b/packages/wasm/crate/tests/test_witness.rs @@ -241,6 +241,7 @@ async fn test_witness() { dutch_auction_end_actions: vec![], dutch_auction_withdraw_actions: vec![], delegator_votes: vec![], + action_liquidity_tournament_vote: vec![], }; // Viewing key to reveal asset balances and transactions. diff --git a/packages/wasm/src/voting.ts b/packages/wasm/src/voting.ts new file mode 100644 index 0000000000..5a936fea58 --- /dev/null +++ b/packages/wasm/src/voting.ts @@ -0,0 +1,23 @@ +import { IdbConstants } from '@penumbra-zone/types/indexed-db'; +import { AddressIndex, IdentityKey } from '@penumbra-zone/protobuf/penumbra/core/keys/v1/keys_pb'; +import { get_voting_notes } from '../wasm/index.js'; +import { SpendableNoteRecord } from '@penumbra-zone/protobuf/penumbra/view/v1/view_pb'; +import { Jsonified } from '@penumbra-zone/types/jsonified'; + +/** + * Utility that returns delegation voting notes to be used in the liquidity tournament. + */ +export const getVotingNotes = async ( + address_index: AddressIndex, + votable_at_height: bigint, + idbConstants: IdbConstants, +): Promise<[SpendableNoteRecord, IdentityKey][]> => { + // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment -- JsValue coming out of wasm + const votingNotes: [Jsonified, Jsonified][] = + await get_voting_notes(address_index.toBinary(), votable_at_height, idbConstants); + + return votingNotes.map(([note, identityKey]) => [ + SpendableNoteRecord.fromJson(note), + IdentityKey.fromJson(identityKey), + ]); +};