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
This commit is contained in:
Sebastian Bor 2021-07-01 12:21:55 +01:00 committed by GitHub
parent 7f60b40d7a
commit 778265c0d6
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
11 changed files with 134 additions and 50 deletions

View File

@ -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'),

View File

@ -33,7 +33,7 @@ export function RealmDepositBadge({
)}`}
</span>
)}
{communityTokenOwnerRecord && councilTokenOwnerRecord && ', '}
{communityTokenOwnerRecord && councilTokenOwnerRecord && ' | '}
{councilTokenOwnerRecord && (
<span>
{`council tokens: ${formatTokenAmount(

View File

@ -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;
}
}

View File

@ -60,6 +60,11 @@ export const GovernanceError: Record<number, string> = [
"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) {

View File

@ -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,
},

View File

@ -259,6 +259,7 @@ export const GOVERNANCE_SCHEMA = new Map<any, any>([
fields: [
['accountType', 'u8'],
['communityMint', 'pubkey'],
['reserved', 'u64'],
['councilMint', { kind: 'option', type: 'pubkey' }],
['name', 'string'],
],
@ -271,6 +272,7 @@ export const GOVERNANCE_SCHEMA = new Map<any, any>([
fields: [
['accountType', 'u8'],
['config', GovernanceConfig],
['reserved', 'u64'],
['proposalCount', 'u32'],
],
},
@ -282,10 +284,13 @@ export const GOVERNANCE_SCHEMA = new Map<any, any>([
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<any, any>([
['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<any, any>([
['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<any, any>([
fields: [
['accountType', 'u8'],
['proposal', 'pubkey'],
['instructionIndex', 'u16'],
['holdUpTime', 'u32'],
['instruction', InstructionData],
['executedAt', { kind: 'option', type: 'u64' }],
['executionStatus', { kind: 'option', type: 'u8' }],
],
},
],

View File

@ -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())

View File

@ -76,7 +76,7 @@ export function InstructionCard({
<>
<p>{`${LABELS.INSTRUCTION}: ${instructionDetails.dataBase64}`}</p>
<p>
{LABELS.HOLD_UP_TIME_DAYS}: {instruction.info.holdUpTime}
{LABELS.HOLD_UP_TIME_DAYS}: {instruction.info.holdUpTime / 86400}
</p>
</>
}

View File

@ -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<Governance>;
onCreateInstruction: (instruction: TransactionInstruction) => void;
}) => {
const { wallet } = useWallet();
if (!wallet?.publicKey) {
return <div>Wallet not connected</div>;
}
const onCreate = async ({ bufferAddress }: { bufferAddress: string }) => {
const upgradeIx = await createUpgradeInstruction(
governance.info.config.governedAccount,
new PublicKey(bufferAddress),
governance.pubkey,
wallet.publicKey!,
);
onCreateInstruction(upgradeIx);
};
return (
<Form {...formVerticalLayout} form={form} onFinish={onCreate}>
<Form {...formDefaults} form={form} onFinish={onCreate}>
<Form.Item label="program id">
<ExplorerLink
address={governance.info.config.governedAccount}
@ -136,6 +144,9 @@ const UpgradeProgramForm = ({
<Form.Item label="upgrade authority (governance account)">
<ExplorerLink address={governance.pubkey} type="address" />
</Form.Item>
<Form.Item label="spill account (wallet)">
<ExplorerLink address={wallet.publicKey} type="address" />
</Form.Item>
<Form.Item
name="bufferAddress"
label="buffer address"
@ -179,7 +190,7 @@ const MintToForm = ({
return (
<Form
{...formVerticalLayout}
{...formDefaults}
form={form}
onFinish={onCreate}
initialValues={{ amount: 1 }}

View File

@ -12,7 +12,7 @@ import { useProposalAuthority } from '../../../hooks/apiHooks';
import { insertInstruction } from '../../../actions/insertInstruction';
import '../style.less';
import { formVerticalLayout } from '../../../tools/forms';
import { formDefaults } from '../../../tools/forms';
import InstructionInput from './InstructionInput';
const { useWallet } = contexts.Wallet;
@ -47,7 +47,7 @@ export function NewInstructionCard({
proposal,
proposalAuthority!.pubkey,
index,
values.holdUpTime,
values.holdUpTime * 86400,
values.instruction,
);
@ -57,18 +57,20 @@ export function NewInstructionCard({
}
};
const minHoldUpTime = governance.info.config.minInstructionHoldUpTime / 86400;
return !proposalAuthority ? null : (
<Card
title="New Instruction"
actions={[<SaveOutlined key="save" onClick={form.submit} />]}
>
<Form
{...formVerticalLayout}
{...formDefaults}
form={form}
name="control-hooks"
onFinish={onFinish}
initialValues={{
holdUpTime: governance.info.config.minInstructionHoldUpTime,
holdUpTime: minHoldUpTime,
}}
>
<Form.Item
@ -76,15 +78,12 @@ export function NewInstructionCard({
label={LABELS.HOLD_UP_TIME_DAYS}
rules={[{ required: true }]}
>
<InputNumber
maxLength={64}
min={governance.info.config.minInstructionHoldUpTime}
/>
<InputNumber min={minHoldUpTime} />
</Form.Item>
<Form.Item
name="instruction"
label="Instruction"
label="instruction"
rules={[{ required: true }]}
>
<InstructionInput governance={governance}></InstructionInput>

View File

@ -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,
});