From 778265c0d61b1c36ec2eb3ed9ff3a09a48c2d305 Mon Sep 17 00:00:00 2001 From: Sebastian Bor Date: Thu, 1 Jul 2021 12:21:55 +0100 Subject: [PATCH] Governance: future proofing for v2 (#124) * chore: update Governance account * chore: update Proposal account * chore: Update ProposalInstruction account * chore: Update Realm account * chore: update TokenOwnerRecord account * fix: add spill address to upgrade instruction * fix: display units for hold up time * chore: update program id --- packages/common/src/utils/ids.ts | 8 +- .../RealmDepositBadge/realmDepositBadge.tsx | 2 +- packages/governance/src/models/accounts.ts | 87 +++++++++++++++---- packages/governance/src/models/errors.ts | 5 ++ .../governance/src/models/sdkInstructions.ts | 3 +- .../governance/src/models/serialisation.ts | 32 ++++--- .../src/views/proposal/ProposalView.tsx | 4 +- .../proposal/components/InstructionCard.tsx | 2 +- .../proposal/components/InstructionInput.tsx | 19 +++- .../components/NewInstructionCard.tsx | 17 ++-- .../src/views/realm/registerGovernance.tsx | 5 +- 11 files changed, 134 insertions(+), 50 deletions(-) diff --git a/packages/common/src/utils/ids.ts b/packages/common/src/utils/ids.ts index b9130eb..72b98c8 100644 --- a/packages/common/src/utils/ids.ts +++ b/packages/common/src/utils/ids.ts @@ -67,7 +67,7 @@ export const PROGRAM_IDS = [ { name: 'mainnet-beta', governance: () => ({ - programId: new PublicKey('GovergMfhoNZePj4v86rLXZSN4DeFSLmvKEgWCch1Zuu'), + programId: new PublicKey('GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw'), }), wormhole: () => ({ pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'), @@ -88,7 +88,7 @@ export const PROGRAM_IDS = [ { name: 'testnet', governance: () => ({ - programId: new PublicKey('GovergMfhoNZePj4v86rLXZSN4DeFSLmvKEgWCch1Zuu'), + programId: new PublicKey('GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw'), }), wormhole: () => ({ pubkey: new PublicKey('5gQf5AUhAgWYgUCt9ouShm9H7dzzXUsLdssYwe5krKhg'), @@ -107,7 +107,7 @@ export const PROGRAM_IDS = [ { name: 'devnet', governance: () => ({ - programId: new PublicKey('GovergMfhoNZePj4v86rLXZSN4DeFSLmvKEgWCch1Zuu'), + programId: new PublicKey('GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw'), }), wormhole: () => ({ pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'), @@ -125,7 +125,7 @@ export const PROGRAM_IDS = [ { name: 'localnet', governance: () => ({ - programId: new PublicKey('GovergMfhoNZePj4v86rLXZSN4DeFSLmvKEgWCch1Zuu'), + programId: new PublicKey('GovER5Lthms3bLBqWub97yVrMmEogzX7xNjdXpPPCVZw'), }), wormhole: () => ({ pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'), diff --git a/packages/governance/src/components/RealmDepositBadge/realmDepositBadge.tsx b/packages/governance/src/components/RealmDepositBadge/realmDepositBadge.tsx index ba87d15..92f5c51 100644 --- a/packages/governance/src/components/RealmDepositBadge/realmDepositBadge.tsx +++ b/packages/governance/src/components/RealmDepositBadge/realmDepositBadge.tsx @@ -33,7 +33,7 @@ export function RealmDepositBadge({ )}`} )} - {communityTokenOwnerRecord && councilTokenOwnerRecord && ', '} + {communityTokenOwnerRecord && councilTokenOwnerRecord && ' | '} {councilTokenOwnerRecord && ( {`council tokens: ${formatTokenAmount( diff --git a/packages/governance/src/models/accounts.ts b/packages/governance/src/models/accounts.ts index d8587e7..c6e9580 100644 --- a/packages/governance/src/models/accounts.ts +++ b/packages/governance/src/models/accounts.ts @@ -1,6 +1,8 @@ import { PublicKey } from '@solana/web3.js'; import BN from 'bn.js'; -import { utils } from '@oyster/common'; +import { utils, constants } from '@oyster/common'; + +const { ZERO } = constants; /// Seed prefix for Governance Program PDAs export const GOVERNANCE_PROGRAM_SEED = 'governance'; @@ -58,21 +60,45 @@ export function getAccountTypes(accountClass: GovernanceAccountClass) { } } +export enum VoteThresholdPercentageType { + YesVote, + Quorum, +} + +export enum VoteWeightSource { + Deposit, + Snapshot, +} + +export enum InstructionExecutionStatus { + Success, + Error, +} + +export enum InstructionExecutionFlags { + Ordered, + UseTransaction, +} + export class Realm { accountType = GovernanceAccountType.Realm; communityMint: PublicKey; + reserved: BN; + councilMint: PublicKey | undefined; name: string; constructor(args: { communityMint: PublicKey; + reserved: BN; councilMint: PublicKey | undefined; name: string; }) { this.communityMint = args.communityMint; + this.reserved = args.reserved; this.councilMint = args.councilMint; this.name = args.name; } @@ -81,31 +107,42 @@ export class Realm { export class GovernanceConfig { realm: PublicKey; governedAccount: PublicKey; - yesVoteThresholdPercentage: number; - minTokensToCreateProposal: number; + voteThresholdPercentageType: VoteThresholdPercentageType; + voteThresholdPercentage: number; + minTokensToCreateProposal: BN; minInstructionHoldUpTime: number; maxVotingTime: number; + voteWeightSource: VoteWeightSource; + proposalCoolOffTime: number; constructor(args: { realm: PublicKey; governedAccount: PublicKey; - yesVoteThresholdPercentage: number; - minTokensToCreateProposal: number; + voteThresholdPercentageType?: VoteThresholdPercentageType; + voteThresholdPercentage: number; + minTokensToCreateProposal: BN; minInstructionHoldUpTime: number; maxVotingTime: number; + voteWeightSource?: VoteWeightSource; + proposalCoolOffTime?: number; }) { this.realm = args.realm; this.governedAccount = args.governedAccount; - this.yesVoteThresholdPercentage = args.yesVoteThresholdPercentage; + this.voteThresholdPercentageType = + args.voteThresholdPercentageType ?? VoteThresholdPercentageType.YesVote; + this.voteThresholdPercentage = args.voteThresholdPercentage; this.minTokensToCreateProposal = args.minTokensToCreateProposal; this.minInstructionHoldUpTime = args.minInstructionHoldUpTime; this.maxVotingTime = args.maxVotingTime; + this.voteWeightSource = args.voteWeightSource ?? VoteWeightSource.Deposit; + this.proposalCoolOffTime = args.proposalCoolOffTime ?? 0; } } export class Governance { accountType: GovernanceAccountType; config: GovernanceConfig; + reserved: BN; proposalCount: number; isProgramGovernance() { @@ -127,10 +164,12 @@ export class Governance { constructor(args: { accountType: number; config: GovernanceConfig; + reserved?: BN; proposalCount: number; }) { this.accountType = args.accountType; this.config = args.config; + this.reserved = args.reserved ?? ZERO; this.proposalCount = args.proposalCount; } } @@ -146,12 +185,14 @@ export class TokenOwnerRecord { governingTokenDepositAmount: BN; - governanceDelegate?: PublicKey; - unrelinquishedVotesCount: number; totalVotesCount: number; + reserved: BN; + + governanceDelegate?: PublicKey; + constructor(args: { realm: PublicKey; governingTokenMint: PublicKey; @@ -159,6 +200,7 @@ export class TokenOwnerRecord { governingTokenDepositAmount: BN; unrelinquishedVotesCount: number; totalVotesCount: number; + reserved: BN; }) { this.realm = args.realm; this.governingTokenMint = args.governingTokenMint; @@ -166,6 +208,7 @@ export class TokenOwnerRecord { this.governingTokenDepositAmount = args.governingTokenDepositAmount; this.unrelinquishedVotesCount = args.unrelinquishedVotesCount; this.totalVotesCount = args.totalVotesCount; + this.reserved = args.reserved; } } @@ -222,31 +265,35 @@ export class Proposal { signatoriesSignedOffCount: number; - descriptionLink: string; - - name: string; - yesVotesCount: BN; noVotesCount: BN; + instructionsExecutedCount: number; + + instructionsCount: number; + + instructionsNextIndex: number; + draftAt: BN; signingOffAt: BN | null; votingAt: BN | null; + votingAtSlot: BN | null; + votingCompletedAt: BN | null; executingAt: BN | null; closedAt: BN | null; - instructionsExecutedCount: number; + executionFlags: InstructionExecutionFlags | null; - instructionsCount: number; + name: string; - instructionsNextIndex: number; + descriptionLink: string; constructor(args: { governance: PublicKey; @@ -262,12 +309,14 @@ export class Proposal { draftAt: BN; signingOffAt: BN | null; votingAt: BN | null; + votingAtSlot: BN | null; votingCompletedAt: BN | null; executingAt: BN | null; closedAt: BN | null; instructionsExecutedCount: number; instructionsCount: number; instructionsNextIndex: number; + executionFlags: InstructionExecutionFlags; }) { this.governance = args.governance; this.governingTokenMint = args.governingTokenMint; @@ -282,12 +331,14 @@ export class Proposal { this.draftAt = args.draftAt; this.signingOffAt = args.signingOffAt; this.votingAt = args.votingAt; + this.votingAtSlot = args.votingAtSlot; this.votingCompletedAt = args.votingCompletedAt; this.executingAt = args.executingAt; this.closedAt = args.closedAt; this.instructionsExecutedCount = args.instructionsExecutedCount; this.instructionsCount = args.instructionsCount; this.instructionsNextIndex = args.instructionsNextIndex; + this.executionFlags = args.executionFlags; } } @@ -391,19 +442,25 @@ export class InstructionData { export class ProposalInstruction { accountType = GovernanceAccountType.ProposalInstruction; proposal: PublicKey; + instructionIndex: number; holdUpTime: number; instruction: InstructionData; executedAt: BN | null; + executionStatus: InstructionExecutionStatus | null; constructor(args: { proposal: PublicKey; + instructionIndex: number; holdUpTime: number; instruction: InstructionData; executedAt: BN | null; + executionStatus: InstructionExecutionStatus | null; }) { this.proposal = args.proposal; + this.instructionIndex = args.instructionIndex; this.holdUpTime = args.holdUpTime; this.instruction = args.instruction; this.executedAt = args.executedAt; + this.executionStatus = args.executionStatus; } } diff --git a/packages/governance/src/models/errors.ts b/packages/governance/src/models/errors.ts index 52794a0..cc8632d 100644 --- a/packages/governance/src/models/errors.ts +++ b/packages/governance/src/models/errors.ts @@ -60,6 +60,11 @@ export const GovernanceError: Record = [ "Provided upgrade authority doesn't match current program upgrade authority", // InvalidUpgradeAuthority 'Current program upgrade authority must sign transaction', // UpgradeAuthorityMustSign 'Given program is not upgradable', //ProgramNotUpgradable + 'Invalid token owner', //InvalidTokenOwner + 'Current token owner must sign transaction', // TokenOwnerMustSign + 'Given VoteThresholdPercentageType is not supported', //VoteThresholdPercentageTypeNotSupported + 'Given VoteWeightSource is not supported', //VoteWeightSourceNotSupported + 'Proposal cool off time is not supported', // ProposalCoolOffTimeNotSupported ]; export function getTransactionErrorMsg(error: SendTransactionError) { diff --git a/packages/governance/src/models/sdkInstructions.ts b/packages/governance/src/models/sdkInstructions.ts index c6e258e..4c974b6 100644 --- a/packages/governance/src/models/sdkInstructions.ts +++ b/packages/governance/src/models/sdkInstructions.ts @@ -10,6 +10,7 @@ export async function createUpgradeInstruction( programId: PublicKey, bufferAddress: PublicKey, governance: PublicKey, + spillAddress: PublicKey, ) { const PROGRAM_IDS = utils.programIds(); @@ -35,7 +36,7 @@ export async function createUpgradeInstruction( isSigner: false, }, { - pubkey: governance, + pubkey: spillAddress, isWritable: true, isSigner: false, }, diff --git a/packages/governance/src/models/serialisation.ts b/packages/governance/src/models/serialisation.ts index 26a56e6..bc7b669 100644 --- a/packages/governance/src/models/serialisation.ts +++ b/packages/governance/src/models/serialisation.ts @@ -259,6 +259,7 @@ export const GOVERNANCE_SCHEMA = new Map([ fields: [ ['accountType', 'u8'], ['communityMint', 'pubkey'], + ['reserved', 'u64'], ['councilMint', { kind: 'option', type: 'pubkey' }], ['name', 'string'], ], @@ -271,6 +272,7 @@ export const GOVERNANCE_SCHEMA = new Map([ fields: [ ['accountType', 'u8'], ['config', GovernanceConfig], + ['reserved', 'u64'], ['proposalCount', 'u32'], ], }, @@ -282,10 +284,13 @@ export const GOVERNANCE_SCHEMA = new Map([ fields: [ ['realm', 'pubkey'], ['governedAccount', 'pubkey'], - ['yesVoteThresholdPercentage', 'u8'], - ['minTokensToCreateProposal', 'u16'], + ['voteThresholdPercentageType', 'u8'], + ['voteThresholdPercentage', 'u8'], + ['minTokensToCreateProposal', 'u64'], ['minInstructionHoldUpTime', 'u32'], ['maxVotingTime', 'u32'], + ['voteWeightSource', 'u8'], + ['proposalCoolOffTime', 'u32'], ], }, ], @@ -299,9 +304,10 @@ export const GOVERNANCE_SCHEMA = new Map([ ['governingTokenMint', 'pubkey'], ['governingTokenOwner', 'pubkey'], ['governingTokenDepositAmount', 'u64'], - ['governanceDelegate', { kind: 'option', type: 'pubkey' }], ['unrelinquishedVotesCount', 'u32'], ['totalVotesCount', 'u32'], + ['reserved', 'u64'], + ['governanceDelegate', { kind: 'option', type: 'pubkey' }], ], }, ], @@ -317,19 +323,21 @@ export const GOVERNANCE_SCHEMA = new Map([ ['tokenOwnerRecord', 'pubkey'], ['signatoriesCount', 'u8'], ['signatoriesSignedOffCount', 'u8'], - ['descriptionLink', 'string'], - ['name', 'string'], ['yesVotesCount', 'u64'], ['noVotesCount', 'u64'], - ['draftAt', 'u64'], - ['signingOffAt', { kind: 'option', type: 'u64' }], - ['votingAt', { kind: 'option', type: 'u64' }], - ['votingCompletedAt', { kind: 'option', type: 'u64' }], - ['executingAt', { kind: 'option', type: 'u64' }], - ['closedAt', { kind: 'option', type: 'u64' }], ['instructionsExecutedCount', 'u16'], ['instructionsCount', 'u16'], ['instructionsNextIndex', 'u16'], + ['draftAt', 'u64'], + ['signingOffAt', { kind: 'option', type: 'u64' }], + ['votingAt', { kind: 'option', type: 'u64' }], + ['votingAtSlot', { kind: 'option', type: 'u64' }], + ['votingCompletedAt', { kind: 'option', type: 'u64' }], + ['executingAt', { kind: 'option', type: 'u64' }], + ['closedAt', { kind: 'option', type: 'u64' }], + ['executionFlags', { kind: 'option', type: 'u8' }], + ['name', 'string'], + ['descriptionLink', 'string'], ], }, ], @@ -375,9 +383,11 @@ export const GOVERNANCE_SCHEMA = new Map([ fields: [ ['accountType', 'u8'], ['proposal', 'pubkey'], + ['instructionIndex', 'u16'], ['holdUpTime', 'u32'], ['instruction', InstructionData], ['executedAt', { kind: 'option', type: 'u64' }], + ['executionStatus', { kind: 'option', type: 'u8' }], ], }, ], diff --git a/packages/governance/src/views/proposal/ProposalView.tsx b/packages/governance/src/views/proposal/ProposalView.tsx index b00da38..8f5b2bd 100644 --- a/packages/governance/src/views/proposal/ProposalView.tsx +++ b/packages/governance/src/views/proposal/ProposalView.tsx @@ -466,10 +466,10 @@ function getMinRequiredYesVoteScore( governingTokenMint: MintInfo, ): string { const minVotes = - governance.info.config.yesVoteThresholdPercentage === 100 + governance.info.config.voteThresholdPercentage === 100 ? governingTokenMint.supply : governingTokenMint.supply - .mul(new BN(governance.info.config.yesVoteThresholdPercentage)) + .mul(new BN(governance.info.config.voteThresholdPercentage)) .div(new BN(100)); return new BigNumber(minVotes.toString()) diff --git a/packages/governance/src/views/proposal/components/InstructionCard.tsx b/packages/governance/src/views/proposal/components/InstructionCard.tsx index 1786371..36b2526 100644 --- a/packages/governance/src/views/proposal/components/InstructionCard.tsx +++ b/packages/governance/src/views/proposal/components/InstructionCard.tsx @@ -76,7 +76,7 @@ export function InstructionCard({ <>

{`${LABELS.INSTRUCTION}: ${instructionDetails.dataBase64}`}

- {LABELS.HOLD_UP_TIME_DAYS}: {instruction.info.holdUpTime} + {LABELS.HOLD_UP_TIME_DAYS}: {instruction.info.holdUpTime / 86400}

} diff --git a/packages/governance/src/views/proposal/components/InstructionInput.tsx b/packages/governance/src/views/proposal/components/InstructionInput.tsx index 2afbcc8..2a0b01f 100644 --- a/packages/governance/src/views/proposal/components/InstructionInput.tsx +++ b/packages/governance/src/views/proposal/components/InstructionInput.tsx @@ -1,5 +1,5 @@ import { PlusCircleOutlined } from '@ant-design/icons'; -import { ExplorerLink, ParsedAccount, utils } from '@oyster/common'; +import { ExplorerLink, ParsedAccount, utils, contexts } from '@oyster/common'; import { Token } from '@solana/spl-token'; import { PublicKey, TransactionInstruction } from '@solana/web3.js'; import { @@ -18,7 +18,8 @@ import { AccountFormItem } from '../../../components/AccountFormItem/accountForm import { Governance } from '../../../models/accounts'; import { createUpgradeInstruction } from '../../../models/sdkInstructions'; import { serializeInstructionToBase64 } from '../../../models/serialisation'; -import { formDefaults, formVerticalLayout } from '../../../tools/forms'; +import { formDefaults } from '../../../tools/forms'; +const { useWallet } = contexts.Wallet; export default function InstructionInput({ governance, @@ -116,17 +117,24 @@ const UpgradeProgramForm = ({ governance: ParsedAccount; onCreateInstruction: (instruction: TransactionInstruction) => void; }) => { + const { wallet } = useWallet(); + + if (!wallet?.publicKey) { + return
Wallet not connected
; + } + const onCreate = async ({ bufferAddress }: { bufferAddress: string }) => { const upgradeIx = await createUpgradeInstruction( governance.info.config.governedAccount, new PublicKey(bufferAddress), governance.pubkey, + wallet.publicKey!, ); onCreateInstruction(upgradeIx); }; return ( -
+ + + + ]} > - + diff --git a/packages/governance/src/views/realm/registerGovernance.tsx b/packages/governance/src/views/realm/registerGovernance.tsx index 6ab660a..afedd6b 100644 --- a/packages/governance/src/views/realm/registerGovernance.tsx +++ b/packages/governance/src/views/realm/registerGovernance.tsx @@ -15,6 +15,7 @@ import { useKeyParam } from '../../hooks/useKeyParam'; import { ModalFormAction } from '../../components/ModalFormAction/modalFormAction'; import { AccountFormItem } from '../../components/AccountFormItem/accountFormItem'; +import BN from 'bn.js'; const { useWallet } = contexts.Wallet; const { useConnection } = contexts.Connection; @@ -42,8 +43,8 @@ export function RegisterGovernance({ const config = new GovernanceConfig({ realm: realmKey, governedAccount: new PublicKey(values.governedAccountAddress), - yesVoteThresholdPercentage: values.yesVoteThresholdPercentage, - minTokensToCreateProposal: values.minTokensToCreateProposal, + voteThresholdPercentage: values.yesVoteThresholdPercentage, + minTokensToCreateProposal: new BN(values.minTokensToCreateProposal), minInstructionHoldUpTime: values.minInstructionHoldUpTime * 86400, maxVotingTime: values.maxVotingTime * 86400, });