From ff6f988fe752f45ee8a70bb691bd6096e72cb0f9 Mon Sep 17 00:00:00 2001 From: Wan <495709+wa0x6e@users.noreply.github.com> Date: Wed, 21 Feb 2024 23:19:21 +0800 Subject: [PATCH] feat: add single choice and approval voting type (#46) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * fix: show voting buttons for offchain proposals * refactor: move the network actions to actions.ts * feat: add support for basic voting for offchain proposals * fix: fix invalid import type * fix: use correct sequencer network depending on chainId * chore: add tests * Update packages/sx.js/src/clients/offchain/ethereum-sig/index.ts Co-authored-by: Wiktor Tkaczyński * refactor: use same function order definition as other networks * fix: handle offchain receipt without tx_hash * fix: fix typing * fix: throw error on sequencer error response * chore: add unit test * chore: fix tests * fix: show voting form only for supported voting type * chore: remove cumbersome describe block * fix: switch to correct chain for offchain actions * fix: avoid changing type * chore: improve test * fix: revert network enforcer * fix: remove unused import * refactor: rename voteType * refactor: rename type * feat: add support for single choice voting * fix: remove redundant tooltip * feat: add support for approval voting * chore: fix tests * refactor: extract list to constant * chore: update changeset * fix: UI fix * Update .changeset/chilly-avocados-teach.md Co-authored-by: Wiktor Tkaczyński * Update apps/ui/src/networks/offchain/helpers.ts Co-authored-by: Wiktor Tkaczyński * fix: fix missing parenthesis * fix: fix botched merged * refactor: extract vote form into their own component * refactor: add more type to Choice * fix(ui): fix glitched focus ring * fix(ui): remove unused class * fix(ui): DRY class * fix: fix Choice type * Update packages/sx.js/src/clients/starknet/starknet-tx/index.ts Co-authored-by: Wiktor Tkaczyński * refactor: rename events name * refactor: improve event naming * fix: tentative type fix --------- Co-authored-by: Wiktor Tkaczyński --- .changeset/chilly-avocados-teach.md | 5 ++ apps/ui/src/components/ProposalVote.vue | 11 +++- .../src/components/ProposalVoteApproval.vue | 49 ++++++++++++++++++ apps/ui/src/components/ProposalVoteBasic.vue | 50 +++++++++++++++++++ .../components/ProposalVoteSingleChoice.vue | 26 ++++++++++ apps/ui/src/components/ProposalsListItem.vue | 35 +++---------- apps/ui/src/helpers/constants.ts | 1 + apps/ui/src/networks/offchain/actions.ts | 3 +- apps/ui/src/networks/offchain/helpers.ts | 20 ++++++-- apps/ui/src/types.ts | 2 +- apps/ui/src/views/Proposal.vue | 46 +++++++---------- .../clients/offchain/ethereum-sig/index.ts | 9 +++- .../clients/offchain/ethereum-sig/types.ts | 15 +++++- packages/sx.js/src/clients/offchain/types.ts | 7 +-- .../integration/offchain/fixtures/vote.json | 3 +- .../__snapshots__/index.test.ts.snap | 1 + .../offchain/ethereum-sig/index.test.ts | 3 +- 17 files changed, 210 insertions(+), 76 deletions(-) create mode 100644 .changeset/chilly-avocados-teach.md create mode 100644 apps/ui/src/components/ProposalVoteApproval.vue create mode 100644 apps/ui/src/components/ProposalVoteBasic.vue create mode 100644 apps/ui/src/components/ProposalVoteSingleChoice.vue diff --git a/.changeset/chilly-avocados-teach.md b/.changeset/chilly-avocados-teach.md new file mode 100644 index 000000000..b2211473d --- /dev/null +++ b/.changeset/chilly-avocados-teach.md @@ -0,0 +1,5 @@ +--- +"@snapshot-labs/sx": patch +--- + +add approval and single choice vote support to OffchainEthereumSig diff --git a/apps/ui/src/components/ProposalVote.vue b/apps/ui/src/components/ProposalVote.vue index 61458c208..99f42fc3f 100644 --- a/apps/ui/src/components/ProposalVote.vue +++ b/apps/ui/src/components/ProposalVote.vue @@ -1,4 +1,5 @@ @@ -50,5 +55,7 @@ const isSupported = computed(() => { Voting for this proposal is not supported - +
+ +
diff --git a/apps/ui/src/components/ProposalVoteApproval.vue b/apps/ui/src/components/ProposalVoteApproval.vue new file mode 100644 index 000000000..c8bb54e3b --- /dev/null +++ b/apps/ui/src/components/ProposalVoteApproval.vue @@ -0,0 +1,49 @@ + + + diff --git a/apps/ui/src/components/ProposalVoteBasic.vue b/apps/ui/src/components/ProposalVoteBasic.vue new file mode 100644 index 000000000..962b19480 --- /dev/null +++ b/apps/ui/src/components/ProposalVoteBasic.vue @@ -0,0 +1,50 @@ + + + diff --git a/apps/ui/src/components/ProposalVoteSingleChoice.vue b/apps/ui/src/components/ProposalVoteSingleChoice.vue new file mode 100644 index 000000000..685a8fdf3 --- /dev/null +++ b/apps/ui/src/components/ProposalVoteSingleChoice.vue @@ -0,0 +1,26 @@ + + + diff --git a/apps/ui/src/components/ProposalsListItem.vue b/apps/ui/src/components/ProposalsListItem.vue index 4c12159b1..67a0d7235 100644 --- a/apps/ui/src/components/ProposalsListItem.vue +++ b/apps/ui/src/components/ProposalsListItem.vue @@ -80,35 +80,12 @@ async function handleVoteClick(choice: Choice) {
-
- - - - - - - - - - - - - - - -
+
diff --git a/apps/ui/src/helpers/constants.ts b/apps/ui/src/helpers/constants.ts index b821e43b1..bdb331ec9 100644 --- a/apps/ui/src/helpers/constants.ts +++ b/apps/ui/src/helpers/constants.ts @@ -23,3 +23,4 @@ export const COINGECKO_BASE_ASSETS = { export const MAX_SYMBOL_LENGTH = 12; export const CHOICES = ['For', 'Against', 'Abstain']; +export const SUPPORTED_VOTING_TYPES = ['basic', 'single-choice', 'approval']; diff --git a/apps/ui/src/networks/offchain/actions.ts b/apps/ui/src/networks/offchain/actions.ts index df21b91e5..24d50afbb 100644 --- a/apps/ui/src/networks/offchain/actions.ts +++ b/apps/ui/src/networks/offchain/actions.ts @@ -39,7 +39,8 @@ export function createActions( const data = { space: proposal.space.id, proposal: proposal.proposal_id as string, - choice: getSdkChoice(choice), + type: proposal.type, + choice: getSdkChoice(proposal.type, choice), authenticator: '', strategies: [], metadataUri: '' diff --git a/apps/ui/src/networks/offchain/helpers.ts b/apps/ui/src/networks/offchain/helpers.ts index c68fae6de..07d015a83 100644 --- a/apps/ui/src/networks/offchain/helpers.ts +++ b/apps/ui/src/networks/offchain/helpers.ts @@ -1,7 +1,19 @@ import { Choice } from '@/types'; -export function getSdkChoice(choice: Choice): number { - if (choice === 'for') return 1; - if (choice === 'against') return 2; - return 3; +export function getSdkChoice(type: string, choice: Choice): number | number[] { + if (type === 'basic') { + if (choice === 'for') return 1; + if (choice === 'against') return 2; + return 3; + } + + if (type === 'single-choice') { + return choice as number; + } + + if (type === 'approval') { + return choice as number[]; + } + + throw new Error('Vote type not supported'); } diff --git a/apps/ui/src/types.ts b/apps/ui/src/types.ts index 0963a6a34..5e3447f0e 100644 --- a/apps/ui/src/types.ts +++ b/apps/ui/src/types.ts @@ -16,7 +16,7 @@ export type NetworkID = | 'matic' | 'arb1'; -export type Choice = 'for' | 'against' | 'abstain'; +export type Choice = 'for' | 'against' | 'abstain' | number | number[]; export type SelectedStrategy = { address: string; diff --git a/apps/ui/src/views/Proposal.vue b/apps/ui/src/views/Proposal.vue index 45c128abf..52489b290 100644 --- a/apps/ui/src/views/Proposal.vue +++ b/apps/ui/src/views/Proposal.vue @@ -162,35 +162,23 @@ watchEffect(() => {

Cast your vote

-
- - - - - - - - - - - - - - - -
+ + +
diff --git a/packages/sx.js/src/clients/offchain/ethereum-sig/index.ts b/packages/sx.js/src/clients/offchain/ethereum-sig/index.ts index d9b92d06a..b1c32449f 100644 --- a/packages/sx.js/src/clients/offchain/ethereum-sig/index.ts +++ b/packages/sx.js/src/clients/offchain/ethereum-sig/index.ts @@ -1,5 +1,5 @@ import { offchainGoerli } from '../../../offchainNetworks'; -import { domain, voteTypes } from './types'; +import { domain, SingleChoiceVoteTypes, MultipleChoiceVoteTypes } from './types'; import type { Signer, TypedDataSigner, TypedDataField } from '@ethersproject/abstract-signer'; import type { Vote, Envelope, SignatureData, EIP712VoteMessage, EIP712Message } from '../types'; import type { OffchainNetworkConfig } from '../../../types'; @@ -93,7 +93,12 @@ export class EthereumSig { metadata: '' }; - const signatureData = await this.sign(signer, message, voteTypes); + let voteType = SingleChoiceVoteTypes; + if (data.type === 'approval') { + voteType = MultipleChoiceVoteTypes; + } + + const signatureData = await this.sign(signer, message, voteType); return { signatureData, diff --git a/packages/sx.js/src/clients/offchain/ethereum-sig/types.ts b/packages/sx.js/src/clients/offchain/ethereum-sig/types.ts index 7c7261b60..0369f34b3 100644 --- a/packages/sx.js/src/clients/offchain/ethereum-sig/types.ts +++ b/packages/sx.js/src/clients/offchain/ethereum-sig/types.ts @@ -3,7 +3,7 @@ export const domain = { version: '0.1.4' }; -export const voteTypes = { +export const SingleChoiceVoteTypes = { Vote: [ { name: 'from', type: 'address' }, { name: 'space', type: 'string' }, @@ -15,3 +15,16 @@ export const voteTypes = { { name: 'metadata', type: 'string' } ] }; + +export const MultipleChoiceVoteTypes = { + Vote: [ + { name: 'from', type: 'address' }, + { name: 'space', type: 'string' }, + { name: 'timestamp', type: 'uint64' }, + { name: 'proposal', type: 'string' }, + { name: 'choice', type: 'uint32[]' }, + { name: 'reason', type: 'string' }, + { name: 'app', type: 'string' }, + { name: 'metadata', type: 'string' } + ] +}; diff --git a/packages/sx.js/src/clients/offchain/types.ts b/packages/sx.js/src/clients/offchain/types.ts index 2ed76adb0..87dd8e022 100644 --- a/packages/sx.js/src/clients/offchain/types.ts +++ b/packages/sx.js/src/clients/offchain/types.ts @@ -1,10 +1,6 @@ import type { TypedDataDomain, TypedDataField } from '@ethersproject/abstract-signer'; -enum Choice { - Against = 2, - For = 1, - Abstain = 3 -} +type Choice = number | number[]; export type SignatureData = { address: string; @@ -43,4 +39,5 @@ export type Vote = { proposal: string; choice: Choice; metadataUri: string; + type: string; }; diff --git a/packages/sx.js/test/integration/offchain/fixtures/vote.json b/packages/sx.js/test/integration/offchain/fixtures/vote.json index 13e78527e..e3f6e796a 100644 --- a/packages/sx.js/test/integration/offchain/fixtures/vote.json +++ b/packages/sx.js/test/integration/offchain/fixtures/vote.json @@ -4,5 +4,6 @@ "strategies": [], "proposal": "0xcc47146e5e0ac781e8976405a8da4468f2a0c4cdf0c7659353d728fafe46f801", "choice": 1, - "metadataUri": "" + "metadataUri": "", + "type": "basic" } diff --git a/packages/sx.js/test/unit/clients/offchain/ethereum-sig/__snapshots__/index.test.ts.snap b/packages/sx.js/test/unit/clients/offchain/ethereum-sig/__snapshots__/index.test.ts.snap index 1395f4cf0..d1bfa3eb1 100644 --- a/packages/sx.js/test/unit/clients/offchain/ethereum-sig/__snapshots__/index.test.ts.snap +++ b/packages/sx.js/test/unit/clients/offchain/ethereum-sig/__snapshots__/index.test.ts.snap @@ -9,6 +9,7 @@ exports[`EthereumSig > should create vote envelope 1`] = ` "proposal": "0xcc47146e5e0ac781e8976405a8da4468f2a0c4cdf0c7659353d728fafe46f801", "space": "wan-test.eth", "strategies": [], + "type": "basic", }, "signatureData": { "address": "0xf1f09AdC06aAB740AA16004D62Dbd89484d3Be90", diff --git a/packages/sx.js/test/unit/clients/offchain/ethereum-sig/index.test.ts b/packages/sx.js/test/unit/clients/offchain/ethereum-sig/index.test.ts index 68d949f5c..76e0e0c8f 100644 --- a/packages/sx.js/test/unit/clients/offchain/ethereum-sig/index.test.ts +++ b/packages/sx.js/test/unit/clients/offchain/ethereum-sig/index.test.ts @@ -26,7 +26,8 @@ describe('EthereumSig', () => { strategies: [], proposal: '0xcc47146e5e0ac781e8976405a8da4468f2a0c4cdf0c7659353d728fafe46f801', choice: 1, - metadataUri: '' + metadataUri: '', + type: 'basic' }; const envelope = await client.vote({