Skip to content

Commit

Permalink
Delegator voting action view (#1539)
Browse files Browse the repository at this point in the history
* Support delegator voting action view

* Action view support
  • Loading branch information
grod220 authored Jul 23, 2024
1 parent 4e30796 commit 86c1bbe
Show file tree
Hide file tree
Showing 7 changed files with 231 additions and 1 deletion.
7 changes: 7 additions & 0 deletions .changeset/late-dancers-check.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
---
'@penumbra-zone/perspective': minor
'@penumbra-zone/getters': minor
'@repo/ui': minor
---

Add support for delegate vote action views
6 changes: 6 additions & 0 deletions packages/getters/src/delegator-vote-view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
import { createGetter } from './utils/create-getter.js';
import { DelegatorVoteView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/governance/v1/governance_pb.js';

export const getDelegatorVoteBody = createGetter(
(view?: DelegatorVoteView) => view?.delegatorVote.value?.delegatorVote?.body,
);
9 changes: 9 additions & 0 deletions packages/perspective/src/translators/action-view.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@ import { asOpaqueOutputView, asReceiverOutputView } from './output-view.js';
import { Address } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/keys/v1/keys_pb.js';
import { asOpaqueSwapView } from './swap-view.js';
import { asOpaqueSwapClaimView } from './swap-claim-view.js';
import { asOpaqueDelegatorVoteView } from './delegator-vote-view.js';

export const asPublicActionView: Translator<ActionView> = actionView => {
switch (actionView?.actionView.case) {
Expand Down Expand Up @@ -40,6 +41,14 @@ export const asPublicActionView: Translator<ActionView> = actionView => {
},
});

case 'delegatorVote':
return new ActionView({
actionView: {
case: 'delegatorVote',
value: asOpaqueDelegatorVoteView(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
Expand Down
60 changes: 60 additions & 0 deletions packages/perspective/src/translators/delegator-vote-view.test.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,60 @@
import { asOpaqueDelegatorVoteView } from './delegator-vote-view.js';
import { describe, expect, test } from 'vitest';
import {
DelegatorVote,
DelegatorVoteView,
DelegatorVoteView_Opaque,
DelegatorVoteView_Visible,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/governance/v1/governance_pb.js';
import { NoteView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/shielded_pool/v1/shielded_pool_pb.js';
import { ValueView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb.js';

describe('asOpaqueDelegatorVoteView', () => {
test('when passed `undefined` returns an empty, opaque delegator vote view', () => {
const expected = new DelegatorVoteView({
delegatorVote: {
case: 'opaque',
value: new DelegatorVoteView_Opaque({
delegatorVote: undefined,
}),
},
});

expect(asOpaqueDelegatorVoteView(undefined)).toEqual(expected);
});

test('when passed an already-opaque delegator vote view returns the delegator vote view as-is', () => {
const opaqueDelegatorVoteView = new DelegatorVoteView({
delegatorVote: {
case: 'opaque',
value: new DelegatorVoteView_Opaque({
delegatorVote: new DelegatorVote({ body: { proposal: 123n } }),
}),
},
});
expect(
asOpaqueDelegatorVoteView(opaqueDelegatorVoteView).equals(opaqueDelegatorVoteView),
).toBeTruthy();
});

test('returns an opaque version of the delegator vote view', () => {
const visibleDelegatorVoteView = new DelegatorVoteView({
delegatorVote: {
case: 'visible',
value: new DelegatorVoteView_Visible({
delegatorVote: new DelegatorVote({ body: { proposal: 123n } }),
note: new NoteView({ value: new ValueView() }),
}),
},
});

const result = asOpaqueDelegatorVoteView(visibleDelegatorVoteView);

expect(result.delegatorVote.case).toBe('opaque');
expect(
result.delegatorVote.value?.delegatorVote?.equals(
visibleDelegatorVoteView.delegatorVote.value?.delegatorVote,
),
).toBeTruthy();
});
});
20 changes: 20 additions & 0 deletions packages/perspective/src/translators/delegator-vote-view.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
import { Translator } from './types.js';
import {
DelegatorVoteView,
DelegatorVoteView_Opaque,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/governance/v1/governance_pb.js';

export const asOpaqueDelegatorVoteView: Translator<DelegatorVoteView> = delegatorVoteView => {
if (delegatorVoteView?.delegatorVote.case === 'opaque') {
return delegatorVoteView;
}

return new DelegatorVoteView({
delegatorVote: {
case: 'opaque',
value: new DelegatorVoteView_Opaque({
delegatorVote: delegatorVoteView?.delegatorVote.value?.delegatorVote,
}),
},
});
};
3 changes: 2 additions & 1 deletion packages/ui/components/ui/tx/action-view.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,7 @@ import { ActionDutchAuctionScheduleViewComponent } from './actions-views/action-
import { ActionDutchAuctionEndComponent } from './actions-views/action-dutch-auction-end';
import { ActionDutchAuctionWithdrawViewComponent } from './actions-views/action-dutch-auction-withdraw-view';
import { ValueView } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb.js';
import { DelegatorVoteComponent } from './actions-views/delegator-vote.tsx';

const CASE_TO_LABEL: Record<string, string> = {
daoDeposit: 'DAO Deposit',
Expand Down Expand Up @@ -106,7 +107,7 @@ export const ActionViewComponent = ({
return <UnimplementedView label='Validator Vote' />;

case 'delegatorVote':
return <UnimplementedView label='Delegator Vote' />;
return <DelegatorVoteComponent value={actionView.value} />;

case 'proposalDepositClaim':
return <UnimplementedView label='Proposal Deposit Claim' />;
Expand Down
127 changes: 127 additions & 0 deletions packages/ui/components/ui/tx/actions-views/delegator-vote.tsx
Original file line number Diff line number Diff line change
@@ -0,0 +1,127 @@
import { ViewBox } from '../viewbox';
import { ValueViewComponent } from '../../value';
import { getAddress } from '@penumbra-zone/getters/note-view';
import { ActionDetails } from './action-details';
import {
DelegatorVoteBody,
DelegatorVoteView,
Vote,
Vote_Vote,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/component/governance/v1/governance_pb';
import { getDelegatorVoteBody } from '@penumbra-zone/getters/delegator-vote-view';
import { Amount } from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/num/v1/num_pb.js';
import {
Metadata,
ValueView,
} from '@buf/penumbra-zone_penumbra.bufbuild_es/penumbra/core/asset/v1/asset_pb.js';
import { AddressViewComponent } from '../../address-view';
import { base64ToUint8Array } from '@penumbra-zone/types/base64';

// TODO: This is sad, but at the moment, we aren't provided the metadata to have a rich display.
// Given the high-priority of getting action view support, this is added.
// We should properly implement ValueViews into the protos for DelegatorVote action view and delete this code.
const umMetadata = new Metadata({
denomUnits: [
{
denom: 'penumbra',
exponent: 6,
},
{
denom: 'upenumbra',
exponent: 0,
},
],
base: 'upenumbra',
display: 'penumbra',
symbol: 'UM',
penumbraAssetId: {
inner: base64ToUint8Array('KeqcLzNx9qSH5+lcJHBB9KNW+YPrBk5dKzvPMiypahA='),
},
images: [
{
svg: 'https://raw.githubusercontent.com/prax-wallet/registry/main/images/um.svg',
},
],
});

const umValueView = (amount?: Amount) => {
return new ValueView({
valueView: {
case: 'knownAssetId',
value: {
amount,
metadata: umMetadata,
},
},
});
};

export const DelegatorVoteComponent = ({ value }: { value: DelegatorVoteView }) => {
const body = getDelegatorVoteBody.optional()(value);

if (value.delegatorVote.case === 'visible') {
const note = value.delegatorVote.value.note;
const address = getAddress.optional()(note);

return (
<ViewBox
label='Delegator Vote'
visibleContent={
<ActionDetails>
<VoteBodyDetails body={body} />
<ActionDetails.Row label='Account'>
<AddressViewComponent view={address} />
</ActionDetails.Row>
</ActionDetails>
}
/>
);
}

if (value.delegatorVote.case === 'opaque') {
return (
<ViewBox
label='Delegator Vote'
visibleContent={
<ActionDetails>
<VoteBodyDetails body={body} />
</ActionDetails>
}
/>
);
}

return <div>Invalid DelegatorVoteView</div>;
};

const VoteToString = (vote: Vote): string => {
switch (vote.vote) {
case Vote_Vote.UNSPECIFIED:
return 'UNSPECIFIED';
case Vote_Vote.ABSTAIN:
return 'ABSTAIN';
case Vote_Vote.YES:
return 'YES';
case Vote_Vote.NO:
return 'NO';
}
};

const VoteBodyDetails = ({ body }: { body?: DelegatorVoteBody }) => {
return (
<>
{!!body?.proposal && (
<ActionDetails.Row label='Proposal'>{Number(body.proposal)}</ActionDetails.Row>
)}

{!!body?.vote && (
<ActionDetails.Row label='Vote'>{VoteToString(body.vote)}</ActionDetails.Row>
)}
{!!body?.unbondedAmount && (
<ActionDetails.Row label='Voting power'>
<ValueViewComponent view={umValueView(body.unbondedAmount)} />
</ActionDetails.Row>
)}
</>
);
};

0 comments on commit 86c1bbe

Please sign in to comment.