Merge remote-tracking branch 'origin/main' into feature/m

This commit is contained in:
bartosz-lipinski 2021-04-27 22:30:30 -05:00
commit 3891bdcc47
85 changed files with 744 additions and 747 deletions

View File

@ -8,6 +8,6 @@
"typescript.enablePromptUseWorkspaceTsdk": true,
"prettier.prettierPath": ".vscode/pnpify/prettier/index.js",
"cSpell.words": [
"Timelock"
]
}

View File

@ -46,7 +46,7 @@ let WORMHOLE_BRIDGE: {
wrappedMaster: string;
};
let TIMELOCK: {
let GOVERNANCE: {
programId: PublicKey;
};
@ -66,7 +66,7 @@ export const ENABLE_FEES_INPUT = false;
export const PROGRAM_IDS = [
{
name: 'mainnet-beta',
timelock: () => ({
governance: () => ({
programId: new PublicKey('9iAeqqppjn7g1Jn8o2cQCqU5aQVV3h4q9bbWdKRbeC2w'),
}),
wormhole: () => ({
@ -87,7 +87,7 @@ export const PROGRAM_IDS = [
},
{
name: 'testnet',
timelock: () => ({
governance: () => ({
programId: new PublicKey('DCVPuhaGNMLh73FRWFroH4o3ERUhBKMRWfBgJV94VqRk'),
}),
wormhole: () => ({
@ -105,7 +105,7 @@ export const PROGRAM_IDS = [
},
{
name: 'devnet',
timelock: () => ({
governance: () => ({
programId: new PublicKey('DCVPuhaGNMLh73FRWFroH4o3ERUhBKMRWfBgJV94VqRk'),
}),
wormhole: () => ({
@ -123,8 +123,8 @@ export const PROGRAM_IDS = [
},
{
name: 'localnet',
timelock: () => ({
programId: new PublicKey('3KEiR9eX7isb8xeFzTzbLZij8tKH6YFYUbMyjBp8ygDK'),
governance: () => ({
programId: new PublicKey('2uWrXQ3tMurqTLe3Dmue6DzasUGV9UPqK7AK7HzS7v3D'),
}),
wormhole: () => ({
pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'),
@ -155,7 +155,7 @@ export const setProgramIds = (envName: string) => {
SWAP_PROGRAM_LAYOUT = swap.current.layout;
SWAP_PROGRAM_LEGACY_IDS = swap.legacy;
TIMELOCK = instance.timelock();
GOVERNANCE = instance.governance();
if (envName === 'mainnet-beta') {
LENDING_PROGRAM_ID = new PublicKey(
@ -172,7 +172,7 @@ export const programIds = () => {
swapLayout: SWAP_PROGRAM_LAYOUT,
lending: LENDING_PROGRAM_ID,
wormhole: WORMHOLE_BRIDGE,
timelock: TIMELOCK,
governance: GOVERNANCE,
associatedToken: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
bpf_upgrade_loader: BPF_UPGRADE_LOADER_ID,
system: SYSTEM,

View File

@ -10,7 +10,7 @@ module.exports = {
webpack: {
configure: (webpackConfig, { env, paths }) => {
paths.appBuild = webpackConfig.output.path = path.resolve(
'./../../build/proposals',
'./../../build/governance',
);
return webpackConfig;
},

View File

@ -1,5 +1,5 @@
{
"name": "proposals",
"name": "governance",
"version": "0.0.1",
"dependencies": {
"@ant-design/icons": "^4.4.0",
@ -60,8 +60,8 @@
"localnet:down": "solana-localnet down",
"localnet:logs": "solana-localnet logs -f",
"predeploy": "git pull --ff-only && yarn && yarn build",
"deploy": "gh-pages -d ../../build/proposals --repo https://github.com/solana-labs/oyster-gov",
"deploy:ar": "arweave deploy-dir ../../build/proposals --key-file ",
"deploy": "gh-pages -d ../../build/governance --repo https://github.com/solana-labs/oyster-gov",
"deploy:ar": "arweave deploy-dir ../../build/governance --key-file ",
"format:fix": "prettier --write \"**/*.+(js|jsx|ts|tsx|json|css|md)\""
},
"eslintConfig": {

View File

Before

Width:  |  Height:  |  Size: 5.3 KiB

After

Width:  |  Height:  |  Size: 5.3 KiB

View File

Before

Width:  |  Height:  |  Size: 216 KiB

After

Width:  |  Height:  |  Size: 216 KiB

View File

@ -8,10 +8,10 @@ import {
import { contexts, utils, models, ParsedAccount } from '@oyster/common';
import {
GOVERNANCE_AUTHORITY_SEED,
CustomSingleSignerTimelockTransactionLayout,
TimelockSet,
TimelockState,
} from '../models/timelock';
CustomSingleSignerTransactionLayout,
Proposal,
ProposalState,
} from '../models/governance';
import { addCustomSingleSignerTransactionInstruction } from '../models/addCustomSingleSignerTransaction';
const { sendTransaction } = contexts.Connection;
@ -21,8 +21,8 @@ const { approve } = models;
export const addCustomSingleSignerTransaction = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
state: ParsedAccount<TimelockState>,
proposal: ParsedAccount<Proposal>,
state: ParsedAccount<ProposalState>,
sigAccount: PublicKey,
slot: string,
instruction: string,
@ -34,7 +34,7 @@ export const addCustomSingleSignerTransaction = async (
let instructions: TransactionInstruction[] = [];
const rentExempt = await connection.getMinimumBalanceForRentExemption(
CustomSingleSignerTimelockTransactionLayout.span,
CustomSingleSignerTransactionLayout.span,
);
const txnKey = new Account();
@ -43,13 +43,13 @@ export const addCustomSingleSignerTransaction = async (
fromPubkey: wallet.publicKey,
newAccountPubkey: txnKey.publicKey,
lamports: rentExempt,
space: CustomSingleSignerTimelockTransactionLayout.span,
programId: PROGRAM_IDS.timelock.programId,
space: CustomSingleSignerTransactionLayout.span,
programId: PROGRAM_IDS.governance.programId,
});
const [authority] = await PublicKey.findProgramAddress(
[Buffer.from(GOVERNANCE_AUTHORITY_SEED), proposal.pubkey.toBuffer()],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
signers.push(txnKey);

View File

@ -14,9 +14,9 @@ import {
import {
GOVERNANCE_AUTHORITY_SEED,
TimelockSet,
TimelockState,
} from '../models/timelock';
Proposal,
ProposalState,
} from '../models/governance';
import { AccountLayout } from '@solana/spl-token';
import { addSignerInstruction } from '../models/addSigner';
const { createTokenAccount } = actions;
@ -27,8 +27,8 @@ const { approve } = models;
export const addSigner = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
state: ParsedAccount<TimelockState>,
proposal: ParsedAccount<Proposal>,
state: ParsedAccount<ProposalState>,
adminAccount: PublicKey,
newSignatoryAccountOwner: PublicKey,
) => {
@ -52,7 +52,7 @@ export const addSigner = async (
const [mintAuthority] = await PublicKey.findProgramAddress(
[Buffer.from(GOVERNANCE_AUTHORITY_SEED), proposal.pubkey.toBuffer()],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
const transferAuthority = approve(

View File

@ -14,13 +14,13 @@ import {
} from '@oyster/common';
import { AccountLayout, MintLayout } from '@solana/spl-token';
import { initTimelockSetInstruction } from '../models/initTimelockSet';
import { initProposalInstruction } from '../models/initProposal';
import {
GOVERNANCE_AUTHORITY_SEED,
TimelockConfig,
TimelockSetLayout,
TimelockStateLayout,
} from '../models/timelock';
Governance,
ProposalLayout,
ProposalStateLayout,
} from '../models/governance';
const { cache } = contexts.Accounts;
const { sendTransactions } = contexts.Connection;
@ -33,7 +33,7 @@ export const createProposal = async (
name: string,
description: string,
useGovernance: boolean,
timelockConfig: ParsedAccount<TimelockConfig>,
governance: ParsedAccount<Governance>,
): Promise<Account> => {
const PROGRAM_IDS = utils.programIds();
@ -51,12 +51,12 @@ export const createProposal = async (
await cache.queryMint(
connection,
useGovernance
? timelockConfig.info.governanceMint
: timelockConfig.info.councilMint!,
? governance.info.governanceMint
: governance.info.councilMint!,
)
).decimals;
const timelockSetKey = new Account();
const proposalKey = new Account();
const {
sigMint,
@ -79,54 +79,54 @@ export const createProposal = async (
wallet,
accountRentExempt,
mintRentExempt,
timelockConfig,
governance,
useGovernance,
sourceMintDecimals,
timelockSetKey,
proposalKey,
);
let createTimelockAccountsSigners: Account[] = [];
let createTimelockAccountsInstructions: TransactionInstruction[] = [];
let createGovernanceAccountsSigners: Account[] = [];
let createGovernanceAccountsInstructions: TransactionInstruction[] = [];
const timelockRentExempt = await connection.getMinimumBalanceForRentExemption(
TimelockSetLayout.span,
const proposalRentExempt = await connection.getMinimumBalanceForRentExemption(
ProposalLayout.span,
);
const timelockStateRentExempt = await connection.getMinimumBalanceForRentExemption(
TimelockStateLayout.span,
const proposalStateRentExempt = await connection.getMinimumBalanceForRentExemption(
ProposalStateLayout.span,
);
const timelockStateKey = new Account();
const proposalStateKey = new Account();
const uninitializedTimelockStateInstruction = SystemProgram.createAccount({
const uninitializedProposalStateInstruction = SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: timelockStateKey.publicKey,
lamports: timelockStateRentExempt,
space: TimelockStateLayout.span,
programId: PROGRAM_IDS.timelock.programId,
newAccountPubkey: proposalStateKey.publicKey,
lamports: proposalStateRentExempt,
space: ProposalStateLayout.span,
programId: PROGRAM_IDS.governance.programId,
});
signers.push(timelockStateKey);
createTimelockAccountsSigners.push(timelockStateKey);
createTimelockAccountsInstructions.push(
uninitializedTimelockStateInstruction,
signers.push(proposalStateKey);
createGovernanceAccountsSigners.push(proposalStateKey);
createGovernanceAccountsInstructions.push(
uninitializedProposalStateInstruction,
);
const uninitializedTimelockSetInstruction = SystemProgram.createAccount({
const uninitializedProposalInstruction = SystemProgram.createAccount({
fromPubkey: wallet.publicKey,
newAccountPubkey: timelockSetKey.publicKey,
lamports: timelockRentExempt,
space: TimelockSetLayout.span,
programId: PROGRAM_IDS.timelock.programId,
newAccountPubkey: proposalKey.publicKey,
lamports: proposalRentExempt,
space: ProposalLayout.span,
programId: PROGRAM_IDS.governance.programId,
});
signers.push(timelockSetKey);
createTimelockAccountsSigners.push(timelockSetKey);
createTimelockAccountsInstructions.push(uninitializedTimelockSetInstruction);
signers.push(proposalKey);
createGovernanceAccountsSigners.push(proposalKey);
createGovernanceAccountsInstructions.push(uninitializedProposalInstruction);
instructions.push(
initTimelockSetInstruction(
timelockStateKey.publicKey,
timelockSetKey.publicKey,
timelockConfig.pubkey,
initProposalInstruction(
proposalStateKey.publicKey,
proposalKey.publicKey,
governance.pubkey,
sigMint,
adminMint,
voteMint,
@ -141,8 +141,8 @@ export const createProposal = async (
noVoteDumpAccount,
sourceHoldingAccount,
useGovernance
? timelockConfig.info.governanceMint
: timelockConfig.info.councilMint!,
? governance.info.governanceMint
: governance.info.councilMint!,
authority,
description,
name,
@ -161,11 +161,12 @@ export const createProposal = async (
wallet,
[
...associatedInstructions,
createTimelockAccountsInstructions,
createGovernanceAccountsInstructions,
instructions,
],
[...associatedSigners, createTimelockAccountsSigners, signers],
SequenceType.Sequential,
[...associatedSigners, createGovernanceAccountsSigners, signers],
true,
true,
);
notify({
@ -174,7 +175,7 @@ export const createProposal = async (
description: `Transaction - ${tx}`,
});
return timelockSetKey;
return proposalKey;
} catch (ex) {
console.error(ex);
throw new Error();
@ -204,7 +205,7 @@ async function getAssociatedAccountsAndInstructions(
wallet: any,
accountRentExempt: number,
mintRentExempt: number,
timelockConfig: ParsedAccount<TimelockConfig>,
governance: ParsedAccount<Governance>,
useGovernance: boolean,
sourceMintDecimals: number,
newProposalKey: Account,
@ -216,7 +217,7 @@ async function getAssociatedAccountsAndInstructions(
Buffer.from(GOVERNANCE_AUTHORITY_SEED),
newProposalKey.publicKey.toBuffer(),
],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
let mintSigners: Account[] = [];
@ -351,8 +352,8 @@ async function getAssociatedAccountsAndInstructions(
wallet.publicKey,
accountRentExempt,
useGovernance
? timelockConfig.info.governanceMint
: timelockConfig.info.councilMint!,
? governance.info.governanceMint
: governance.info.councilMint!,
authority,
holdingSigners,
);

View File

@ -14,10 +14,10 @@ import {
import {
GOVERNANCE_AUTHORITY_SEED,
TimelockConfig,
TimelockSet,
TimelockState,
} from '../models/timelock';
Governance,
Proposal,
ProposalState,
} from '../models/governance';
import { AccountLayout } from '@solana/spl-token';
@ -35,13 +35,13 @@ const { approve } = models;
export const depositSourceTokensAndVote = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
proposal: ParsedAccount<Proposal>,
existingVoteAccount: PublicKey | undefined,
existingYesVoteAccount: PublicKey | undefined,
existingNoVoteAccount: PublicKey | undefined,
sourceAccount: PublicKey,
timelockConfig: ParsedAccount<TimelockConfig>,
state: ParsedAccount<TimelockState>,
governance: ParsedAccount<Governance>,
state: ParsedAccount<ProposalState>,
yesVotingTokenAmount: number,
noVotingTokenAmount: number,
) => {
@ -72,11 +72,11 @@ export const depositSourceTokensAndVote = async (
const [governanceVotingRecord] = await PublicKey.findProgramAddress(
[
Buffer.from(GOVERNANCE_AUTHORITY_SEED),
PROGRAM_IDS.timelock.programId.toBuffer(),
PROGRAM_IDS.governance.programId.toBuffer(),
proposal.pubkey.toBuffer(),
existingVoteAccount.toBuffer(),
],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
if (needToCreateGovAccountToo) {
@ -114,7 +114,7 @@ export const depositSourceTokensAndVote = async (
const [mintAuthority] = await PublicKey.findProgramAddress(
[Buffer.from(GOVERNANCE_AUTHORITY_SEED), proposal.pubkey.toBuffer()],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
const depositAuthority = approve(
@ -166,7 +166,7 @@ export const depositSourceTokensAndVote = async (
proposal.info.noVotingMint,
proposal.info.sourceMint,
proposal.pubkey,
timelockConfig.pubkey,
governance.pubkey,
voteAuthority.publicKey,
mintAuthority,
yesVotingTokenAmount,

View File

@ -0,0 +1,71 @@
import {
Account,
Connection,
Message,
TransactionInstruction,
} from '@solana/web3.js';
import { contexts, utils, ParsedAccount } from '@oyster/common';
import {
Proposal,
ProposalState,
GovernanceTransaction,
} from '../models/governance';
import { executeInstruction } from '../models/execute';
import { LABELS } from '../constants';
import { getMessageAccountInfos } from '../utils/transactions';
const { sendTransaction } = contexts.Connection;
const { notify } = utils;
export const execute = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<Proposal>,
state: ParsedAccount<ProposalState>,
transaction: ParsedAccount<GovernanceTransaction>,
) => {
let signers: Account[] = [];
let instructions: TransactionInstruction[] = [];
const actualMessage = decodeBufferIntoMessage(transaction.info.instruction);
const accountInfos = getMessageAccountInfos(actualMessage);
instructions.push(
executeInstruction(
transaction.pubkey,
state.pubkey,
proposal.pubkey,
actualMessage.accountKeys[actualMessage.instructions[0].programIdIndex],
proposal.info.config,
accountInfos,
),
);
notify({
message: LABELS.EXECUTING,
description: LABELS.PLEASE_WAIT,
type: 'warn',
});
try {
let tx = await sendTransaction(
connection,
wallet,
instructions,
signers,
true,
);
notify({
message: LABELS.EXECUTED,
type: 'success',
description: LABELS.TRANSACTION + ` ${tx}`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
};
function decodeBufferIntoMessage(instruction: number[]): Message {
return Message.from(instruction);
}

View File

@ -12,7 +12,7 @@ import {
SequenceType,
} from '@oyster/common';
import { TimelockConfig } from '../models/timelock';
import { Governance } from '../models/governance';
import { AccountLayout, Token } from '@solana/spl-token';
import { LABELS } from '../constants';
const { createTokenAccount } = actions;
@ -26,7 +26,7 @@ export interface SourceEntryInterface {
export const mintSourceTokens = async (
connection: Connection,
wallet: any,
timelockConfig: ParsedAccount<TimelockConfig>,
governance: ParsedAccount<Governance>,
useGovernance: boolean,
entries: SourceEntryInterface[],
setSavePerc: (num: number) => void,
@ -50,8 +50,8 @@ export const mintSourceTokens = async (
wallet.publicKey,
accountRentExempt,
useGovernance
? timelockConfig.info.governanceMint
: timelockConfig.info.councilMint!,
? governance.info.governanceMint
: governance.info.councilMint!,
e.owner,
signers,
);
@ -60,8 +60,8 @@ export const mintSourceTokens = async (
Token.createMintToInstruction(
PROGRAM_IDS.token,
useGovernance
? timelockConfig.info.governanceMint
: timelockConfig.info.councilMint!,
? governance.info.governanceMint
: governance.info.councilMint!,
e.sourceAccount,
wallet.publicKey,
[],

View File

@ -10,13 +10,13 @@ import { AccountLayout, MintLayout, Token } from '@solana/spl-token';
import {
GOVERNANCE_AUTHORITY_SEED,
ExecutionType,
TimelockConfig,
TimelockType,
Governance,
GovernanceType,
VotingEntryRule,
} from '../models/timelock';
import { initTimelockConfigInstruction } from '../models/initTimelockConfig';
} from '../models/governance';
import { initGovernanceInstruction } from '../models/initGovernance';
import BN from 'bn.js';
import { createEmptyTimelockConfigInstruction } from '../models/createEmptyTimelockConfig';
import { createEmptyGovernanceInstruction } from '../models/createEmptyGovernance';
const { sendTransactions } = contexts.Connection;
const { createMint, createTokenAccount } = actions;
@ -25,7 +25,7 @@ const { notify } = utils;
export const registerProgramGovernance = async (
connection: Connection,
wallet: any,
uninitializedTimelockConfig: Partial<TimelockConfig>,
uninitializedGovernance: Partial<Governance>,
useCouncil: boolean,
): Promise<PublicKey> => {
const PROGRAM_IDS = utils.programIds();
@ -41,13 +41,13 @@ export const registerProgramGovernance = async (
AccountLayout.span,
);
if (!uninitializedTimelockConfig.program)
uninitializedTimelockConfig.program = new Account().publicKey; // Random generation if none given
if (!uninitializedGovernance.program)
uninitializedGovernance.program = new Account().publicKey; // Random generation if none given
if (!uninitializedTimelockConfig.councilMint && useCouncil) {
if (!uninitializedGovernance.councilMint && useCouncil) {
// Initialize the mint, an account for the admin, and give them one council token
// to start their lives with.
uninitializedTimelockConfig.councilMint = createMint(
uninitializedGovernance.councilMint = createMint(
mintInstructions,
wallet.publicKey,
mintRentExempt,
@ -61,7 +61,7 @@ export const registerProgramGovernance = async (
mintInstructions,
wallet.publicKey,
accountRentExempt,
uninitializedTimelockConfig.councilMint,
uninitializedGovernance.councilMint,
wallet.publicKey,
mintSigners,
);
@ -69,7 +69,7 @@ export const registerProgramGovernance = async (
mintInstructions.push(
Token.createMintToInstruction(
PROGRAM_IDS.token,
uninitializedTimelockConfig.councilMint,
uninitializedGovernance.councilMint,
adminsCouncilToken,
wallet.publicKey,
[],
@ -78,10 +78,10 @@ export const registerProgramGovernance = async (
);
}
if (!uninitializedTimelockConfig.governanceMint) {
if (!uninitializedGovernance.governanceMint) {
// Initialize the mint, an account for the admin, and give them one governance token
// to start their lives with.
uninitializedTimelockConfig.governanceMint = createMint(
uninitializedGovernance.governanceMint = createMint(
mintInstructions,
wallet.publicKey,
mintRentExempt,
@ -95,7 +95,7 @@ export const registerProgramGovernance = async (
mintInstructions,
wallet.publicKey,
accountRentExempt,
uninitializedTimelockConfig.governanceMint,
uninitializedGovernance.governanceMint,
wallet.publicKey,
mintSigners,
);
@ -103,7 +103,7 @@ export const registerProgramGovernance = async (
mintInstructions.push(
Token.createMintToInstruction(
PROGRAM_IDS.token,
uninitializedTimelockConfig.governanceMint,
uninitializedGovernance.governanceMint,
adminsGovernanceToken,
wallet.publicKey,
[],
@ -112,52 +112,44 @@ export const registerProgramGovernance = async (
);
}
const councilMintSeed = uninitializedTimelockConfig.councilMint
? uninitializedTimelockConfig.councilMint.toBuffer()
: Buffer.from('');
const [timelockConfigKey] = await PublicKey.findProgramAddress(
const [governanceKey] = await PublicKey.findProgramAddress(
[
Buffer.from(GOVERNANCE_AUTHORITY_SEED),
PROGRAM_IDS.timelock.programId.toBuffer(),
uninitializedTimelockConfig.governanceMint.toBuffer(),
councilMintSeed,
uninitializedTimelockConfig.program.toBuffer(),
uninitializedGovernance.program.toBuffer(),
],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
const [programDataAccount] = await PublicKey.findProgramAddress(
[uninitializedTimelockConfig.program.toBuffer()],
[uninitializedGovernance.program.toBuffer()],
PROGRAM_IDS.bpf_upgrade_loader,
);
instructions.push(
createEmptyTimelockConfigInstruction(
timelockConfigKey,
uninitializedTimelockConfig.program,
createEmptyGovernanceInstruction(
governanceKey,
uninitializedGovernance.program,
programDataAccount,
wallet.publicKey,
uninitializedTimelockConfig.governanceMint,
uninitializedGovernance.governanceMint,
wallet.publicKey,
uninitializedTimelockConfig.councilMint,
uninitializedGovernance.councilMint,
),
);
instructions.push(
initTimelockConfigInstruction(
timelockConfigKey,
uninitializedTimelockConfig.program,
uninitializedTimelockConfig.governanceMint,
initGovernanceInstruction(
governanceKey,
uninitializedGovernance.program,
uninitializedGovernance.governanceMint,
uninitializedTimelockConfig.voteThreshold!,
uninitializedTimelockConfig.executionType || ExecutionType.Independent,
uninitializedTimelockConfig.timelockType ||
TimelockType.CustomSingleSignerV1,
uninitializedTimelockConfig.votingEntryRule || VotingEntryRule.Anytime,
uninitializedTimelockConfig.minimumSlotWaitingPeriod || new BN(0),
uninitializedTimelockConfig.timeLimit || new BN(0),
uninitializedTimelockConfig.name || '',
uninitializedTimelockConfig.councilMint,
uninitializedGovernance.voteThreshold!,
uninitializedGovernance.executionType || ExecutionType.Independent,
uninitializedGovernance.governanceType || GovernanceType.Governance,
uninitializedGovernance.votingEntryRule || VotingEntryRule.Anytime,
uninitializedGovernance.minimumSlotWaitingPeriod || new BN(0),
uninitializedGovernance.timeLimit || new BN(0),
uninitializedGovernance.name || '',
uninitializedGovernance.councilMint,
),
);
@ -184,7 +176,7 @@ export const registerProgramGovernance = async (
description: `Transaction - ${tx}`,
});
return timelockConfigKey;
return governanceKey;
} catch (ex) {
console.error(ex);
throw new Error();

View File

@ -6,7 +6,7 @@ import {
} from '@solana/web3.js';
import { contexts, utils, models, ParsedAccount } from '@oyster/common';
import { GOVERNANCE_AUTHORITY_SEED, TimelockSet } from '../models/timelock';
import { GOVERNANCE_AUTHORITY_SEED, Proposal } from '../models/governance';
import { removeSignerInstruction } from '../models/removeSigner';
const { sendTransaction } = contexts.Connection;
const { notify } = utils;
@ -15,7 +15,7 @@ const { approve } = models;
export const removeSigner = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
proposal: ParsedAccount<Proposal>,
adminAccount: PublicKey,
sigAccount: PublicKey,
) => {
@ -26,7 +26,7 @@ export const removeSigner = async (
const [mintAuthority] = await PublicKey.findProgramAddress(
[Buffer.from(GOVERNANCE_AUTHORITY_SEED), proposal.pubkey.toBuffer()],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
const transferAuthority = approve(

View File

@ -8,9 +8,9 @@ import { contexts, utils, models, ParsedAccount } from '@oyster/common';
import {
GOVERNANCE_AUTHORITY_SEED,
TimelockSet,
TimelockState,
} from '../models/timelock';
Proposal,
ProposalState,
} from '../models/governance';
import { signInstruction } from '../models/sign';
const { sendTransaction } = contexts.Connection;
@ -20,8 +20,8 @@ const { approve } = models;
export const sign = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
state: ParsedAccount<TimelockState>,
proposal: ParsedAccount<Proposal>,
state: ParsedAccount<ProposalState>,
sigAccount: PublicKey,
) => {
const PROGRAM_IDS = utils.programIds();
@ -31,7 +31,7 @@ export const sign = async (
const [mintAuthority] = await PublicKey.findProgramAddress(
[Buffer.from(GOVERNANCE_AUTHORITY_SEED), proposal.pubkey.toBuffer()],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
const transferAuthority = approve(

View File

@ -14,10 +14,10 @@ import {
import {
GOVERNANCE_AUTHORITY_SEED,
TimelockSet,
TimelockState,
TimelockStateStatus,
} from '../models/timelock';
Proposal,
ProposalState,
ProposalStateStatus,
} from '../models/governance';
import { AccountLayout } from '@solana/spl-token';
import { withdrawVotingTokensInstruction } from '../models/withdrawVotingTokens';
import { LABELS } from '../constants';
@ -29,8 +29,8 @@ const { approve } = models;
export const withdrawVotingTokens = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
state: ParsedAccount<TimelockState>,
proposal: ParsedAccount<Proposal>,
state: ParsedAccount<ProposalState>,
existingVoteAccount: PublicKey | undefined,
existingYesVoteAccount: PublicKey | undefined,
existingNoVoteAccount: PublicKey | undefined,
@ -81,7 +81,7 @@ export const withdrawVotingTokens = async (
const [mintAuthority] = await PublicKey.findProgramAddress(
[Buffer.from(GOVERNANCE_AUTHORITY_SEED), proposal.pubkey.toBuffer()],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
// We dont know in this scope how much is in each account so we just ask for all in each.
@ -119,11 +119,11 @@ export const withdrawVotingTokens = async (
const [governanceVotingRecord] = await PublicKey.findProgramAddress(
[
Buffer.from(GOVERNANCE_AUTHORITY_SEED),
PROGRAM_IDS.timelock.programId.toBuffer(),
PROGRAM_IDS.governance.programId.toBuffer(),
proposal.pubkey.toBuffer(),
existingVoteAccount.toBuffer(),
],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
signers.push(transferAuthority);
@ -150,7 +150,7 @@ export const withdrawVotingTokens = async (
);
const [msg, completedMsg] =
state.info.status === TimelockStateStatus.Voting
state.info.status === ProposalStateStatus.Voting
? [LABELS.WITHDRAWING_YOUR_VOTE, LABELS.VOTE_WITHDRAWN]
: [LABELS.REFUNDING_YOUR_TOKENS, LABELS.TOKENS_REFUNDED];

View File

@ -1,53 +1,49 @@
import * as CANNON from 'cannon'
import React, { useState, useEffect, useContext, useRef } from 'react'
import { useFrame } from 'react-three-fiber'
import * as CANNON from 'cannon';
import React, { useState, useEffect, useContext, useRef } from 'react';
import { useFrame } from 'react-three-fiber';
// Cannon-world context provider
export const CannonContext = React.createContext<any | null>(
null,
);
export const CannonContext = React.createContext<any | null>(null);
export function Provider({ children = null as any }) {
// Set up physics
const [world] = useState(() => new CANNON.World());
useEffect(() => {
world.broadphase = new CANNON.NaiveBroadphase()
world.solver.iterations = 10
world.gravity.set(0, 0, -25)
world.broadphase = new CANNON.NaiveBroadphase();
world.solver.iterations = 10;
world.gravity.set(0, 0, -25);
}, [world]);
// Run world stepper every frame
useFrame(() => world.step(1 / 60));
// Distribute world via context
return (
<CannonContext.Provider value={world} children={children} />
);
return <CannonContext.Provider value={world} children={children} />;
}
// Custom hook to maintain a world physics body
export function useCannon({ ...props }, fn: any, deps = []) {
const ref = useRef<any>()
const ref = useRef<any>();
// Get cannon world object
const world = useContext(CannonContext)
const world = useContext(CannonContext);
// Instanciate a physics body
const [body] = useState(() => new CANNON.Body(props))
const [body] = useState(() => new CANNON.Body(props));
useEffect(() => {
// Call function so the user can add shapes
fn(body)
fn(body);
// Add body to world on mount
world.addBody(body)
world.addBody(body);
// Remove body on unmount
return () => world.removeBody(body)
}, deps)
return () => world.removeBody(body);
}, deps); //eslint-disable-line
useFrame(() => {
if (ref.current) {
// Transport cannon physics into the referenced threejs object
ref.current.position.copy(body.position)
ref.current.quaternion.copy(body.quaternion)
ref.current.position.copy(body.position);
ref.current.quaternion.copy(body.quaternion);
}
})
});
return ref
return ref;
}

View File

@ -1,7 +1,7 @@
import React from 'react';
import './../../App.less';
import { Breadcrumb, Layout } from 'antd';
import { Link, useLocation } from 'react-router-dom';
import { Layout } from 'antd';
import { Link } from 'react-router-dom';
import { LABELS } from '../../constants';
import { components } from '@oyster/common';
@ -11,30 +11,31 @@ import Logo from './dark-horizontal-combined-rainbow.inline.svg';
const { AppBar } = components;
export const AppLayout = React.memo((props: any) => {
const location = useLocation();
// const location = useLocation();
const breadcrumbNameMap: any = {
'/governance': 'Governance',
'/apps/1': 'Application1',
'/apps/2': 'Application2',
'/apps/1/detail': 'Detail',
'/apps/2/detail': 'Detail',
};
// const breadcrumbNameMap: any = {
// '/governance': 'Governance',
// '/apps/1': 'Application1',
// '/apps/2': 'Application2',
// '/apps/1/detail': 'Detail',
// '/apps/2/detail': 'Detail',
// };
const pathSnippets = location.pathname.split('/').filter(i => i);
const extraBreadcrumbItems = pathSnippets.map((_, index) => {
const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
return (
<Breadcrumb.Item key={url}>
<Link to={url}>{breadcrumbNameMap[url]}</Link>
</Breadcrumb.Item>
);
});
const breadcrumbItems = [
<Breadcrumb.Item key="home">
<Link to="/">Home</Link>
</Breadcrumb.Item>,
].concat(extraBreadcrumbItems);
//const pathSnippets = location.pathname.split('/').filter(i => i);
// const extraBreadcrumbItems = pathSnippets.map((_, index) => {
// const url = `/${pathSnippets.slice(0, index + 1).join('/')}`;
// return (
// <Breadcrumb.Item key={url}>
// <Link to={url}>{breadcrumbNameMap[url]}</Link>
// </Breadcrumb.Item>
// );
// });
// const breadcrumbItems = [
// <Breadcrumb.Item key="home">
// <Link to="/">Home</Link>
// </Breadcrumb.Item>,
// ].concat(extraBreadcrumbItems);
// TODO: add breadcrumb

View File

@ -1,7 +1,7 @@
import { ParsedAccount } from '@oyster/common';
import { Button, Modal, Input, Form, Progress } from 'antd';
import React, { useState } from 'react';
import { TimelockSet, TimelockState } from '../../models/timelock';
import { Proposal, ProposalState } from '../../models/governance';
import { utils, contexts, hooks } from '@oyster/common';
import { addSigner } from '../../actions/addSigner';
import { PublicKey } from '@solana/web3.js';
@ -22,8 +22,8 @@ export default function AddSigners({
proposal,
state,
}: {
proposal: ParsedAccount<TimelockSet>;
state: ParsedAccount<TimelockState>;
proposal: ParsedAccount<Proposal>;
state: ParsedAccount<ProposalState>;
}) {
const wallet = useWallet();
const connection = useConnection();

View File

@ -7,17 +7,19 @@ import {
RedoOutlined,
} from '@ant-design/icons';
import { ParsedAccount, contexts } from '@oyster/common';
import { Card } from 'antd';
import { Message } from '@solana/web3.js';
import { Card, Button } from 'antd';
import Meta from 'antd/lib/card/Meta';
import React, { useEffect, useState } from 'react';
import React, { useEffect, useMemo, useState } from 'react';
import { execute } from '../../actions/execute';
import { LABELS } from '../../constants';
import {
TimelockSet,
TimelockState,
TimelockStateStatus,
TimelockTransaction,
} from '../../models/timelock';
Proposal,
ProposalState,
ProposalStateStatus,
GovernanceTransaction,
} from '../../models/governance';
import './style.less';
const { useWallet } = contexts.Wallet;
@ -35,22 +37,33 @@ export function InstructionCard({
state,
position,
}: {
instruction: ParsedAccount<TimelockTransaction>;
proposal: ParsedAccount<TimelockSet>;
state: ParsedAccount<TimelockState>;
instruction: ParsedAccount<GovernanceTransaction>;
proposal: ParsedAccount<Proposal>;
state: ParsedAccount<ProposalState>;
position: number;
}) {
const [tabKey, setTabKey] = useState('info');
const [playing, setPlaying] = useState(
instruction.info.executed === 1 ? Playstate.Played : Playstate.Unplayed,
);
const instructionDetails = useMemo(() => {
const message = Message.from(instruction.info.instruction);
return {
instructionProgramID:
message.accountKeys[message.instructions[0].programIdIndex],
instructionData: message.instructions[0].data,
};
}, [instruction]);
const contentList: Record<string, JSX.Element> = {
info: (
<Meta
title={'Program: TODO'}
title={`${LABELS.PROGRAM_ID}: ${instructionDetails.instructionProgramID}`}
description={
<>
<p>Instruction: TODO</p>
<p>{`${LABELS.INSTRUCTION}: ${instructionDetails.instructionData}`}</p>
<p>
{LABELS.DELAY}: {instruction.info.slot.toNumber()}
</p>
@ -93,9 +106,9 @@ function PlayStatusButton({
setPlaying,
instruction,
}: {
proposal: ParsedAccount<TimelockSet>;
state: ParsedAccount<TimelockState>;
instruction: ParsedAccount<TimelockTransaction>;
proposal: ParsedAccount<Proposal>;
state: ParsedAccount<ProposalState>;
instruction: ParsedAccount<GovernanceTransaction>;
playing: Playstate;
setPlaying: React.Dispatch<React.SetStateAction<Playstate>>;
}) {
@ -131,25 +144,25 @@ function PlayStatusButton({
};
if (
state.info.status !== TimelockStateStatus.Executing &&
state.info.status !== TimelockStateStatus.Completed
state.info.status !== ProposalStateStatus.Executing &&
state.info.status !== ProposalStateStatus.Completed
)
return null;
if (ineligibleToSee) return null;
if (playing === Playstate.Unplayed)
return (
<a onClick={run}>
<Button onClick={run}>
<PlayCircleOutlined style={{ color: 'green' }} key="play" />
</a>
</Button>
);
else if (playing === Playstate.Playing)
return <LoadingOutlined style={{ color: 'orange' }} key="loading" />;
else if (playing === Playstate.Error)
return (
<a onClick={run}>
<Button onClick={run}>
<RedoOutlined style={{ color: 'orange' }} key="play" />
</a>
</Button>
);
else return <CheckCircleOutlined style={{ color: 'green' }} key="played" />;
}

View File

@ -1,7 +1,7 @@
import { ParsedAccount } from '@oyster/common';
import { Button, Modal, Input, Form, Progress, InputNumber, Radio } from 'antd';
import React, { useState } from 'react';
import { TimelockConfig } from '../../models/timelock';
import { Governance } from '../../models/governance';
import { utils, contexts } from '@oyster/common';
import { PublicKey } from '@solana/web3.js';
import { LABELS } from '../../constants';
@ -22,18 +22,18 @@ const layout = {
};
export default function MintSourceTokens({
timelockConfig,
governance,
useGovernance,
}: {
timelockConfig: ParsedAccount<TimelockConfig>;
governance: ParsedAccount<Governance>;
useGovernance: boolean;
}) {
const PROGRAM_IDS = utils.programIds();
const wallet = useWallet();
const connection = useConnection();
const mintKey = useGovernance
? timelockConfig.info.governanceMint
: timelockConfig.info.councilMint!;
? governance.info.governanceMint
: governance.info.councilMint!;
const mint = useMint(mintKey);
const [saving, setSaving] = useState(false);
@ -108,7 +108,7 @@ export default function MintSourceTokens({
try {
if (sourceHolders[i].owner) {
const tokenAccounts = await connection.getTokenAccountsByOwner(
sourceHolders[i].owner || PROGRAM_IDS.timelock,
sourceHolders[i].owner || PROGRAM_IDS.governance,
{
programId: PROGRAM_IDS.token,
},
@ -130,7 +130,7 @@ export default function MintSourceTokens({
await mintSourceTokens(
connection,
wallet.wallet,
timelockConfig,
governance,
useGovernance,
sourceHoldersToRun,
setSavePerc,

View File

@ -3,10 +3,10 @@ import { Card } from 'antd';
import { Form, Input } from 'antd';
import {
INSTRUCTION_LIMIT,
TimelockConfig,
TimelockSet,
TimelockState,
} from '../../models/timelock';
Governance,
Proposal,
ProposalState,
} from '../../models/governance';
import { contexts, ParsedAccount, hooks, utils } from '@oyster/common';
import { addCustomSingleSignerTransaction } from '../../actions/addCustomSingleSignerTransaction';
import { SaveOutlined } from '@ant-design/icons';
@ -28,9 +28,9 @@ export function NewInstructionCard({
position,
config,
}: {
proposal: ParsedAccount<TimelockSet>;
state: ParsedAccount<TimelockState>;
config: ParsedAccount<TimelockConfig>;
proposal: ParsedAccount<Proposal>;
state: ParsedAccount<ProposalState>;
config: ParsedAccount<Governance>;
position: number;
}) {
const [form] = Form.useForm();

View File

@ -3,7 +3,7 @@ import { ParsedAccount, hooks, contexts, utils } from '@oyster/common';
import { Button, Modal } from 'antd';
import React from 'react';
import { sign } from '../../actions/sign';
import { TimelockSet, TimelockState } from '../../models/timelock';
import { Proposal, ProposalState } from '../../models/governance';
const { confirm } = Modal;
const { useWallet } = contexts.Wallet;
@ -15,8 +15,8 @@ export default function SignButton({
proposal,
state,
}: {
proposal: ParsedAccount<TimelockSet>;
state: ParsedAccount<TimelockState>;
proposal: ParsedAccount<Proposal>;
state: ParsedAccount<ProposalState>;
}) {
const wallet = useWallet();
const connection = useConnection();

View File

@ -3,15 +3,15 @@ import { Badge, Tag } from 'antd';
import React from 'react';
import {
STATE_COLOR,
TimelockState,
TimelockStateStatus,
} from '../../models/timelock';
ProposalState,
ProposalStateStatus,
} from '../../models/governance';
export function StateBadgeRibbon({
state,
children,
}: {
state: ParsedAccount<TimelockState>;
state: ParsedAccount<ProposalState>;
children?: any;
}) {
const status = state.info.status;
@ -19,19 +19,19 @@ export function StateBadgeRibbon({
return (
<Badge.Ribbon
style={{ backgroundColor: color }}
text={TimelockStateStatus[status]}
text={ProposalStateStatus[status]}
>
{children}
</Badge.Ribbon>
);
}
export function StateBadge({ state }: { state: ParsedAccount<TimelockState> }) {
export function StateBadge({ state }: { state: ParsedAccount<ProposalState> }) {
const status = state.info.status;
let color = STATE_COLOR[status];
return (
<Tag color={color} style={{ borderWidth: 0 }}>
{TimelockStateStatus[status]}
{ProposalStateStatus[status]}
</Tag>
);
}

View File

@ -2,11 +2,11 @@ import { ParsedAccount } from '@oyster/common';
import { Button, Col, Modal, Row } from 'antd';
import React from 'react';
import {
TimelockConfig,
TimelockSet,
TimelockState,
TimelockStateStatus,
} from '../../models/timelock';
Governance,
Proposal,
ProposalState,
ProposalStateStatus,
} from '../../models/governance';
import { LABELS } from '../../constants';
import { depositSourceTokensAndVote } from '../../actions/depositSourceTokensAndVote';
import { contexts, hooks } from '@oyster/common';
@ -22,12 +22,12 @@ const { confirm } = Modal;
export function Vote({
proposal,
state,
timelockConfig,
governance,
yeahVote,
}: {
proposal: ParsedAccount<TimelockSet>;
state: ParsedAccount<TimelockState>;
timelockConfig: ParsedAccount<TimelockConfig>;
proposal: ParsedAccount<Proposal>;
state: ParsedAccount<ProposalState>;
governance: ParsedAccount<Governance>;
yeahVote: boolean;
}) {
const wallet = useWallet();
@ -42,7 +42,7 @@ export function Vote({
const eligibleToView =
userTokenAccount &&
userTokenAccount.info.amount.toNumber() > 0 &&
state.info.status === TimelockStateStatus.Voting;
state.info.status === ProposalStateStatus.Voting;
const [btnLabel, title, msg, icon] = yeahVote
? [
@ -90,7 +90,7 @@ export function Vote({
yesVoteAccount?.pubkey,
noVoteAccount?.pubkey,
userTokenAccount.pubkey,
timelockConfig,
governance,
state,
yesTokenAmount,
noTokenAmount,

View File

@ -14,10 +14,6 @@ const MAX_BUBBLE_AMOUNT = 50;
export function VoterBubbleGraph(props: IVoterBubbleGraph) {
const { data, width, height, endpoint } = props;
const subdomain = endpoint
.replace('http://', '')
.replace('https://', '')
.split('.')[0];
// For some reason giving this a type causes an issue where setRef
// cant be used with ref={} prop...not sure why. SetStateAction nonsense.
@ -31,24 +27,30 @@ export function VoterBubbleGraph(props: IVoterBubbleGraph) {
d.name.slice(d.name.length - 3, d.name.length),
}));
//console.log('Data', limitedData);
const format = d3.format(',d');
const color = d3
.scaleOrdinal()
.domain([VoteType.Undecided, VoteType.Yes, VoteType.No])
.range(['grey', 'green', 'red']);
const pack = (data: Array<VoterDisplayData>) => {
return d3
.pack()
.size([width - 2, height - 2])
.padding(3)(
//@ts-ignore
d3.hierarchy({ children: data }).sum(d => (d.value ? d.value : 0)),
);
};
useEffect(() => {
if (ref) {
const subdomain = endpoint
.replace('http://', '')
.replace('https://', '')
.split('.')[0];
const format = d3.format(',d');
const color = d3
.scaleOrdinal()
.domain([VoteType.Undecided, VoteType.Yes, VoteType.No])
.range(['grey', 'green', 'red']);
const pack = (data: Array<VoterDisplayData>) => {
return d3
.pack()
.size([width - 2, height - 2])
.padding(3)(
//@ts-ignore
d3.hierarchy({ children: data }).sum(d => (d.value ? d.value : 0)),
);
};
ref.innerHTML = '';
const root = pack(limitedData);
// console.log('Re-rendered');
@ -127,7 +129,7 @@ export function VoterBubbleGraph(props: IVoterBubbleGraph) {
}${format(d.value)}`,
);
}
}, [ref, limitedData, height, width]);
}, [ref, limitedData, height, width, endpoint]);
return (
<div

View File

@ -2,11 +2,10 @@ import { ParsedAccount } from '@oyster/common';
import { Button, Col, Modal, Row } from 'antd';
import React from 'react';
import {
TimelockConfig,
TimelockSet,
TimelockState,
TimelockStateStatus,
} from '../../models/timelock';
Proposal,
ProposalState,
ProposalStateStatus,
} from '../../models/governance';
import { LABELS } from '../../constants';
import { withdrawVotingTokens } from '../../actions/withdrawVotingTokens';
import { contexts, hooks } from '@oyster/common';
@ -21,9 +20,8 @@ export function WithdrawVote({
proposal,
state,
}: {
proposal: ParsedAccount<TimelockSet>;
state: ParsedAccount<TimelockState>;
timelockConfig: ParsedAccount<TimelockConfig>;
proposal: ParsedAccount<Proposal>;
state: ParsedAccount<ProposalState>;
}) {
const wallet = useWallet();
const connection = useConnection();
@ -41,13 +39,13 @@ export function WithdrawVote({
const eligibleToView =
votingTokens > 0 &&
(state.info.status === TimelockStateStatus.Voting ||
state.info.status === TimelockStateStatus.Completed ||
state.info.status === TimelockStateStatus.Executing ||
state.info.status === TimelockStateStatus.Defeated);
(state.info.status === ProposalStateStatus.Voting ||
state.info.status === ProposalStateStatus.Completed ||
state.info.status === ProposalStateStatus.Executing ||
state.info.status === ProposalStateStatus.Defeated);
const [btnLabel, title, msg, action] =
state.info.status === TimelockStateStatus.Voting
state.info.status === ProposalStateStatus.Voting
? [
LABELS.WITHDRAW_VOTE,
LABELS.WITHDRAW_YOUR_VOTE_QUESTION,

View File

@ -99,7 +99,10 @@ export const LABELS = {
TOKENS_REFUNDED: 'Your voting tokens have been refunded',
REGISTER_GOVERNANCE: 'Register',
PROGRAM: 'Program ID',
PROGRAM_ID: 'Program ID',
INSTRUCTION: 'Instruction',
GOVERNANCE: 'Governance Token Holders',
COUNCIL: 'The Council',
GOVERNANCE_MINT: 'Governance Mint ID',

View File

@ -15,26 +15,26 @@ import {
cache,
} from '@oyster/common';
import {
CustomSingleSignerTimelockTransactionLayout,
CustomSingleSignerTimelockTransactionParser,
TimelockConfig,
TimelockConfigLayout,
TimelockConfigParser,
TimelockSet,
TimelockState,
TimelockSetLayout,
TimelockSetParser,
TimelockTransaction,
TimelockStateParser,
TimelockStateLayout,
CustomSingleSignerTimelockTransaction,
} from '../models/timelock';
CustomSingleSignerTransactionLayout,
CustomSingleSignerTransactionParser,
Governance,
GovernanceLayout,
GovernanceParser,
Proposal,
ProposalState,
ProposalLayout,
ProposalParser,
GovernanceTransaction,
ProposalStateParser,
ProposalStateLayout,
CustomSingleSignerTransaction,
} from '../models/governance';
export interface ProposalsContextState {
proposals: Record<string, ParsedAccount<TimelockSet>>;
transactions: Record<string, ParsedAccount<TimelockTransaction>>;
states: Record<string, ParsedAccount<TimelockState>>;
configs: Record<string, ParsedAccount<TimelockConfig>>;
proposals: Record<string, ParsedAccount<Proposal>>;
transactions: Record<string, ParsedAccount<GovernanceTransaction>>;
states: Record<string, ParsedAccount<ProposalState>>;
configs: Record<string, ParsedAccount<Governance>>;
}
export const ProposalsContext = React.createContext<ProposalsContextState | null>(
@ -82,49 +82,47 @@ function useSetupProposalsCache({
setStates: React.Dispatch<React.SetStateAction<{}>>;
setConfigs: React.Dispatch<React.SetStateAction<{}>>;
}) {
const PROGRAM_IDS = utils.programIds();
useEffect(() => {
const PROGRAM_IDS = utils.programIds();
const query = async () => {
const programAccounts = await connection.getProgramAccounts(
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
return programAccounts;
};
Promise.all([query()]).then((all: PublicKeyAndAccount<Buffer>[][]) => {
const newProposals: Record<string, ParsedAccount<TimelockSet>> = {};
const newProposals: Record<string, ParsedAccount<Proposal>> = {};
const newTransactions: Record<
string,
ParsedAccount<TimelockTransaction>
ParsedAccount<GovernanceTransaction>
> = {};
const newStates: Record<string, ParsedAccount<TimelockState>> = {};
const newConfigs: Record<string, ParsedAccount<TimelockConfig>> = {};
const newStates: Record<string, ParsedAccount<ProposalState>> = {};
const newConfigs: Record<string, ParsedAccount<Governance>> = {};
all[0].forEach(a => {
let cached;
switch (a.account.data.length) {
case TimelockSetLayout.span:
cache.add(a.pubkey, a.account, TimelockSetParser);
cached = cache.get(a.pubkey) as ParsedAccount<TimelockSet>;
case ProposalLayout.span:
cache.add(a.pubkey, a.account, ProposalParser);
cached = cache.get(a.pubkey) as ParsedAccount<Proposal>;
newProposals[a.pubkey.toBase58()] = cached;
break;
case CustomSingleSignerTimelockTransactionLayout.span:
cache.add(
case CustomSingleSignerTransactionLayout.span:
cache.add(a.pubkey, a.account, CustomSingleSignerTransactionParser);
cached = cache.get(
a.pubkey,
a.account,
CustomSingleSignerTimelockTransactionParser,
);
cached = cache.get(a.pubkey) as ParsedAccount<TimelockTransaction>;
) as ParsedAccount<GovernanceTransaction>;
newTransactions[a.pubkey.toBase58()] = cached;
break;
case TimelockConfigLayout.span:
cache.add(a.pubkey, a.account, TimelockConfigParser);
cached = cache.get(a.pubkey) as ParsedAccount<TimelockConfig>;
case GovernanceLayout.span:
cache.add(a.pubkey, a.account, GovernanceParser);
cached = cache.get(a.pubkey) as ParsedAccount<Governance>;
newConfigs[a.pubkey.toBase58()] = cached;
break;
case TimelockStateLayout.span:
cache.add(a.pubkey, a.account, TimelockStateParser);
cached = cache.get(a.pubkey) as ParsedAccount<TimelockState>;
case ProposalStateLayout.span:
cache.add(a.pubkey, a.account, ProposalStateParser);
cached = cache.get(a.pubkey) as ParsedAccount<ProposalState>;
newStates[a.pubkey.toBase58()] = cached;
break;
}
@ -136,46 +134,42 @@ function useSetupProposalsCache({
setConfigs(newConfigs);
});
const subID = connection.onProgramAccountChange(
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
async (info: KeyedAccountInfo) => {
const pubkey = typeof info.accountId === 'string' ?
new PublicKey((info.accountId as unknown) as string) :
info.accountId;
[
[TimelockSetLayout.span, TimelockSetParser, setProposals],
[ProposalLayout.span, ProposalParser, setProposals],
[
CustomSingleSignerTimelockTransactionLayout.span,
CustomSingleSignerTimelockTransactionParser,
CustomSingleSignerTransactionLayout.span,
CustomSingleSignerTransactionParser,
setTransactions,
],
[TimelockStateLayout.span, TimelockStateParser, setStates],
[TimelockConfigLayout.span, TimelockConfigParser, setConfigs],
[ProposalStateLayout.span, ProposalStateParser, setStates],
[GovernanceLayout.span, GovernanceParser, setConfigs],
].forEach(arr => {
const [span, parser, setter] = arr;
if (info.accountInfo.data.length === span) {
cache.add(info.accountId, info.accountInfo, parser);
let cached: any;
switch (info.accountInfo.data.length) {
case TimelockSetLayout.span:
cached = cache.get(
pubkey,
) as ParsedAccount<TimelockSet>;
case ProposalLayout.span:
cached = cache.get(info.accountId) as ParsedAccount<Proposal>;
break;
case CustomSingleSignerTimelockTransactionLayout.span:
case CustomSingleSignerTransactionLayout.span:
cached = cache.get(
pubkey,
) as ParsedAccount<CustomSingleSignerTimelockTransaction>;
info.accountId,
) as ParsedAccount<CustomSingleSignerTransaction>;
break;
case TimelockConfigLayout.span:
cached = cache.get(
pubkey,
) as ParsedAccount<TimelockConfig>;
case GovernanceLayout.span:
cached = cache.get(info.accountId) as ParsedAccount<Governance>;
break;
case TimelockStateLayout.span:
case ProposalStateLayout.span:
cached = cache.get(
pubkey,
) as ParsedAccount<TimelockState>;
info.accountId,
) as ParsedAccount<ProposalState>;
break;
}
setter((obj: any) => ({
@ -190,7 +184,7 @@ function useSetupProposalsCache({
return () => {
connection.removeProgramAccountChangeListener(subID);
};
}, [connection, PROGRAM_IDS.timelock.programId.toBase58()]);
}, [connection]); //eslint-disable-line
}
export const useProposals = () => {
const context = useContext(ProposalsContext);

View File

@ -11,7 +11,7 @@ import {
GovernanceVotingRecord,
GovernanceVotingRecordLayout,
GovernanceVotingRecordParser,
} from '../models/timelock';
} from '../models/governance';
import { getGovernanceVotingRecords } from '../utils/lookups';
export function useVotingRecords(proposal: PublicKey) {
@ -31,9 +31,9 @@ export function useVotingRecords(proposal: PublicKey) {
const records = await getGovernanceVotingRecords(proposal, endpoint);
setVotingRecords(records);
const { timelock } = utils.programIds();
const { governance } = utils.programIds();
return connection.onProgramAccountChange(timelock.programId, info => {
return connection.onProgramAccountChange(governance.programId, info => {
if (
info.accountInfo.data.length === GovernanceVotingRecordLayout.span
) {

View File

@ -1,6 +1,6 @@
{
"name": "Oyster Proposals",
"short_name": "Oyster Proposals",
"name": "Oyster Governance",
"short_name": "Oyster Governance",
"display": "standalone",
"start_url": "./",
"theme_color": "#002140",

View File

@ -5,32 +5,32 @@ import * as Layout from '../utils/layout';
import * as BufferLayout from 'buffer-layout';
import {
INSTRUCTION_LIMIT,
TimelockInstruction,
TRANSACTION_SLOTS,
} from './timelock';
GovernanceInstruction,
MAX_TRANSACTIONS,
} from './governance';
import BN from 'bn.js';
/// [Requires Signatory token]
/// Adds a Transaction to the Timelock Set. Max of 10 of any Transaction type. More than 10 will throw error.
/// Adds a Transaction to the Proposal. Max of 10 of any Transaction type. More than 10 will throw error.
/// Creates a PDA using your authority to be used to later execute the instruction.
/// This transaction needs to contain authority to execute the program.
///
/// 0. `[writable]` Uninitialized Timelock Transaction account.
/// 1. `[writable]` Timelock set account.
/// 0. `[writable]` Uninitialized Proposal Transaction account.
/// 1. `[writable]` Proposal account.
/// 2. `[writable]` Signatory account
/// 3. `[writable]` Signatory validation account.
/// 4. `[]` Timelock Set account.
/// 5. `[]` Timelock Config account.
/// 4. `[]` Proposal account.
/// 5. `[]` Governance account.
/// 6. `[]` Transfer authority
/// 7. `[]` Timelock mint authority
/// 7. `[]` Governance mint authority
/// 8. `[]` Token program account.
export const addCustomSingleSignerTransactionInstruction = (
timelockTransactionAccount: PublicKey,
timelockStateAccount: PublicKey,
proposalTransactionAccount: PublicKey,
proposalStateAccount: PublicKey,
signatoryAccount: PublicKey,
signatoryValidationAccount: PublicKey,
timelockSetAccount: PublicKey,
timelockConfigAccount: PublicKey,
proposalAccount: PublicKey,
governanceAccount: PublicKey,
transferAuthority: PublicKey,
authority: PublicKey,
slot: string,
@ -53,9 +53,9 @@ export const addCustomSingleSignerTransactionInstruction = (
);
}
if (position > TRANSACTION_SLOTS) {
if (position > MAX_TRANSACTIONS) {
throw new Error(
'Position is more than ' + TRANSACTION_SLOTS + ' which is not allowed.',
'Position is more than ' + MAX_TRANSACTIONS + ' which is not allowed.',
);
}
@ -75,7 +75,7 @@ export const addCustomSingleSignerTransactionInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.AddCustomSingleSignerTransaction,
instruction: GovernanceInstruction.AddCustomSingleSignerTransaction,
slot: new BN(slot),
instructions: instructionAsBytes,
position: position,
@ -85,12 +85,12 @@ export const addCustomSingleSignerTransactionInstruction = (
);
const keys = [
{ pubkey: timelockTransactionAccount, isSigner: true, isWritable: true },
{ pubkey: timelockStateAccount, isSigner: false, isWritable: true },
{ pubkey: proposalTransactionAccount, isSigner: true, isWritable: true },
{ pubkey: proposalStateAccount, isSigner: false, isWritable: true },
{ pubkey: signatoryAccount, isSigner: false, isWritable: true },
{ pubkey: signatoryValidationAccount, isSigner: false, isWritable: true },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: false },
{ pubkey: timelockConfigAccount, isSigner: false, isWritable: false },
{ pubkey: proposalAccount, isSigner: false, isWritable: false },
{ pubkey: governanceAccount, isSigner: false, isWritable: false },
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
{ pubkey: authority, isSigner: false, isWritable: false },
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
@ -98,7 +98,7 @@ export const addCustomSingleSignerTransactionInstruction = (
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -1,10 +1,10 @@
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
/// [Requires Admin token]
/// Adds a signatory to the Timelock which means that this timelock can't leave Draft state until yet another signatory burns
/// Adds a signatory to the Proposal which means that this Proposal can't leave Draft state until yet another signatory burns
/// their signatory token indicating they are satisfied with the instruction queue. They'll receive an signatory token
/// as a result of this call that they can burn later.
///
@ -12,18 +12,18 @@ import { TimelockInstruction } from './timelock';
/// 1. `[writable]` Initialized Signatory mint account.
/// 2. `[writable]` Admin account.
/// 3. `[writable]` Admin validation account.
/// 4. `[writable]` Timelock set account.
/// 5. `[]` Timelock set account.
/// 4. `[writable]` Proposal account.
/// 5. `[]` Proposal account.
/// 6. `[]` Transfer authority
/// 7. `[]` Timelock program mint authority
/// 7. `[]` Governance program mint authority
/// 8. '[]` Token program id.
export const addSignerInstruction = (
signatoryAccount: PublicKey,
signatoryMintAccount: PublicKey,
adminAccount: PublicKey,
adminValidationAccount: PublicKey,
timelockStateAccount: PublicKey,
timelockSetAccount: PublicKey,
proposalStateAccount: PublicKey,
proposalAccount: PublicKey,
transferAuthority: PublicKey,
mintAuthority: PublicKey,
): TransactionInstruction => {
@ -35,7 +35,7 @@ export const addSignerInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.AddSigner,
instruction: GovernanceInstruction.AddSigner,
},
data,
);
@ -45,15 +45,15 @@ export const addSignerInstruction = (
{ pubkey: signatoryMintAccount, isSigner: false, isWritable: true },
{ pubkey: adminAccount, isSigner: false, isWritable: true },
{ pubkey: adminValidationAccount, isSigner: false, isWritable: true },
{ pubkey: timelockStateAccount, isSigner: false, isWritable: true },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: false },
{ pubkey: proposalStateAccount, isSigner: false, isWritable: true },
{ pubkey: proposalAccount, isSigner: false, isWritable: false },
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
];
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -1,18 +1,18 @@
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
/// 0. `[]` Timelock config key. Needs to be set with pubkey set to PDA with seeds of the
/// program account key, governance mint key, council mint key, and timelock program account key.
/// 1. `[]` Program account to tie this config to.
/// 2. `[]` Governance mint to tie this config to
/// 3. `[]` Payer
/// 4. `[]` Timelock program pub key.
/// 0. `[]` Governance account. The account pubkey needs to be set to PDA with the following seeds:
/// 1) 'governance' const prefix, 2) Governed Program account key
/// 1. `[]` Account of the Program governed by this Governance account
/// 2. `[writable]` Program Data account of the Program governed by this Governance account
/// 3. `[signer]` Current Upgrade Authority account of the Program governed by this Governance account
/// 4. `[signer]` Payer
/// 5. `[]` System account.
/// 6. `[]` Council mint [optional] to tie this config to [Optional]
export const createEmptyTimelockConfigInstruction = (
timelockConfigAccount: PublicKey,
/// 6. `[]` bpf_upgrade_loader account.
export const createEmptyGovernanceInstruction = (
governanceAccount: PublicKey,
programAccount: PublicKey,
programDataAccount: PublicKey,
programUpgradeAuthority: PublicKey,
@ -28,17 +28,17 @@ export const createEmptyTimelockConfigInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.CreateEmptyTimelockConfig,
instruction: GovernanceInstruction.CreateEmptyGovernance,
},
data,
);
const keys = [
{ pubkey: timelockConfigAccount, isSigner: false, isWritable: false },
{ pubkey: governanceAccount, isSigner: false, isWritable: false },
{ pubkey: programAccount, isSigner: false, isWritable: false },
{ pubkey: programDataAccount, isSigner: false, isWritable: true },
{ pubkey: programUpgradeAuthority, isSigner: true, isWritable: false },
{ pubkey: governanceMint, isSigner: false, isWritable: false },
{ pubkey: payer, isSigner: true, isWritable: false },
{ pubkey: PROGRAM_IDS.system, isSigner: false, isWritable: false },
{
@ -48,13 +48,9 @@ export const createEmptyTimelockConfigInstruction = (
},
];
if (councilMint) {
keys.push({ pubkey: councilMint, isSigner: false, isWritable: false });
}
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -1,14 +1,14 @@
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
/// 0. `[]` Governance voting record key. Needs to be set with pubkey set to PDA with seeds of the
/// program account key, proposal key, your voting account key.
/// 1. `[]` Proposal key
/// 2. `[]` Your voting account
/// 3. `[]` Payer
/// 4. `[]` Timelock program pub key.
/// 4. `[]` Governance program pub key
/// 5. `[]` System account.
export const createEmptyGovernanceVotingRecordInstruction = (
governanceRecordAccount: PublicKey,
@ -24,7 +24,7 @@ export const createEmptyGovernanceVotingRecordInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.CreateGovernanceVotingRecord,
instruction: GovernanceInstruction.CreateGovernanceVotingRecord,
},
data,
);
@ -35,7 +35,7 @@ export const createEmptyGovernanceVotingRecordInstruction = (
{ pubkey: votingAccount, isSigner: false, isWritable: false },
{ pubkey: payer, isSigner: true, isWritable: false },
{
pubkey: PROGRAM_IDS.timelock.programId,
pubkey: PROGRAM_IDS.governance.programId,
isSigner: false,
isWritable: false,
},
@ -43,7 +43,7 @@ export const createEmptyGovernanceVotingRecordInstruction = (
];
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -3,22 +3,22 @@ import { utils } from '@oyster/common';
import * as Layout from '../utils/layout';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
import BN from 'bn.js';
/// [Requires tokens of the Governance mint or Council mint depending on type of TimelockSet]
/// Deposits voting tokens to be used during the voting process in a timelock.
/// [Requires tokens of the Governance mint or Council mint depending on type of Proposal]
/// Deposits voting tokens to be used during the voting process in a Proposal.
/// These tokens are removed from your account and can be returned by withdrawing
/// them from the timelock (but then you will miss the vote.)
/// them from the Proposal (but then you will miss the vote.)
///
/// 0. `[writable]` Governance voting record account. See Vote docs for more detail.
/// 1. `[writable]` Initialized Voting account to hold your received voting tokens.
/// 2. `[writable]` User token account to deposit tokens from.
/// 3. `[writable]` Source holding account for timelock that will accept the tokens in escrow.
/// 3. `[writable]` Source holding account for Proposal that will accept the tokens in escrow.
/// 4. `[writable]` Voting mint account.
/// 5. `[]` Timelock set account.
/// 5. `[]` Proposal account.
/// 6. `[]` Transfer authority
/// 7. `[]` Timelock program mint authority
/// 7. `[]` Governance program mint authority (pda with seed of Proposal key)
/// 8. `[]` Token program account.
export const depositSourceTokensInstruction = (
governanceVotingRecord: PublicKey,
@ -26,7 +26,7 @@ export const depositSourceTokensInstruction = (
sourceAccount: PublicKey,
sourceHoldingAccount: PublicKey,
votingMint: PublicKey,
timelockSetAccount: PublicKey,
proposalAccount: PublicKey,
transferAuthority: PublicKey,
mintAuthority: PublicKey,
votingTokenAmount: number,
@ -42,7 +42,7 @@ export const depositSourceTokensInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.DepositGovernanceTokens,
instruction: GovernanceInstruction.DepositGovernanceTokens,
votingTokenAmount: new BN(votingTokenAmount),
},
data,
@ -54,7 +54,7 @@ export const depositSourceTokensInstruction = (
{ pubkey: sourceAccount, isSigner: false, isWritable: true },
{ pubkey: sourceHoldingAccount, isSigner: false, isWritable: true },
{ pubkey: votingMint, isSigner: false, isWritable: true },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: false },
{ pubkey: proposalAccount, isSigner: false, isWritable: false },
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
@ -62,7 +62,7 @@ export const depositSourceTokensInstruction = (
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -5,23 +5,24 @@ import {
} from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
/// Executes a command in the timelock set.
/// Executes a command in the Proposal
///
/// 0. `[writable]` Transaction account you wish to execute.
/// 1. `[writable]` Timelock state account.
/// 1. `[writable]` Proposal state account.
/// 2. `[]` Program being invoked account
/// 3. `[]` Timelock set account.
/// 4. `[]` Timelock config
/// 5. `[]` Clock sysvar.
/// 3. `[]` Proposal account.
/// 4. `[]` Governance account
/// 5. `[]` Governance program account pub key.
/// 6. `[]` Clock sysvar.
/// 7+ Any extra accounts that are part of the instruction, in order
export const executeInstruction = (
transactionAccount: PublicKey,
timelockStateAccount: PublicKey,
timelockSetAccount: PublicKey,
proposalStateAccount: PublicKey,
proposalAccount: PublicKey,
programBeingInvokedAccount: PublicKey,
timelockConfig: PublicKey,
governance: PublicKey,
accountInfos: { pubkey: PublicKey; isWritable: boolean; isSigner: boolean }[],
): TransactionInstruction => {
const PROGRAM_IDS = utils.programIds();
@ -35,7 +36,7 @@ export const executeInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.Execute,
instruction: GovernanceInstruction.Execute,
numberOfExtraAccounts: accountInfos.length,
},
data,
@ -44,16 +45,16 @@ export const executeInstruction = (
const keys = [
// just a note this were all set to writable true...come back and check on this
{ pubkey: transactionAccount, isSigner: false, isWritable: true },
{ pubkey: timelockStateAccount, isSigner: false, isWritable: true },
{ pubkey: proposalStateAccount, isSigner: false, isWritable: true },
{ pubkey: programBeingInvokedAccount, isSigner: false, isWritable: false },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: false },
{ pubkey: timelockConfig, isSigner: false, isWritable: false },
{ pubkey: proposalAccount, isSigner: false, isWritable: false },
{ pubkey: governance, isSigner: false, isWritable: false },
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
...accountInfos,
];
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -8,25 +8,25 @@ export const DESC_SIZE = 200;
export const NAME_SIZE = 32;
export const CONFIG_NAME_LENGTH = 32;
export const INSTRUCTION_LIMIT = 450;
export const TRANSACTION_SLOTS = 5;
export const MAX_TRANSACTIONS = 5;
export const TEMP_FILE_TXN_SIZE = 1000;
/// Seed for proposal authority
export const GOVERNANCE_AUTHORITY_SEED = 'governance';
export enum TimelockInstruction {
InitTimelockSet = 1,
export enum GovernanceInstruction {
InitProposal = 1,
AddSigner = 2,
RemoveSigner = 3,
AddCustomSingleSignerTransaction = 4,
Sign = 8,
Vote = 9,
InitTimelockConfig = 10,
InitGovernance = 10,
Execute = 11,
DepositGovernanceTokens = 12,
WithdrawVotingTokens = 13,
CreateEmptyTimelockConfig = 14,
CreateEmptyGovernance = 14,
CreateGovernanceVotingRecord = 15,
}
@ -58,15 +58,15 @@ export const GovernanceVotingRecordLayout: typeof BufferLayout.Structure = Buffe
],
);
export interface TimelockConfig {
export interface Governance {
/// Account type
accountType: GovernanceAccountType;
/// Vote threshold
voteThreshold: number;
/// Execution type
executionType: ExecutionType;
/// Timelock Type
timelockType: TimelockType;
/// Governance Type
governanceType: GovernanceType;
/// Voting entry rule
votingEntryRule: VotingEntryRule;
/// Minimum slot time-distance from creation of proposal for an instruction to be placed
@ -94,12 +94,12 @@ export enum GovernanceAccountType {
CustomSingleSignerTransaction = 5,
}
export const TimelockConfigLayout: typeof BufferLayout.Structure = BufferLayout.struct(
export const GovernanceLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('accountType'),
BufferLayout.u8('voteThreshold'),
BufferLayout.u8('executionType'),
BufferLayout.u8('timelockType'),
BufferLayout.u8('governanceType'),
BufferLayout.u8('votingEntryRule'),
Layout.uint64('minimumSlotWaitingPeriod'),
Layout.publicKey('governanceMint'),
@ -121,11 +121,11 @@ export enum ExecutionType {
Independent = 0,
}
export enum TimelockType {
CustomSingleSignerV1 = 0,
export enum GovernanceType {
Governance = 0,
}
export enum TimelockStateStatus {
export enum ProposalStateStatus {
/// Draft
Draft = 0,
/// Taking votes
@ -145,21 +145,21 @@ export enum TimelockStateStatus {
}
export const STATE_COLOR: Record<string, string> = {
[TimelockStateStatus.Draft]: 'orange',
[TimelockStateStatus.Voting]: 'blue',
[TimelockStateStatus.Executing]: 'green',
[TimelockStateStatus.Completed]: 'purple',
[TimelockStateStatus.Deleted]: 'gray',
[TimelockStateStatus.Defeated]: 'red',
[ProposalStateStatus.Draft]: 'orange',
[ProposalStateStatus.Voting]: 'blue',
[ProposalStateStatus.Executing]: 'green',
[ProposalStateStatus.Completed]: 'purple',
[ProposalStateStatus.Deleted]: 'gray',
[ProposalStateStatus.Defeated]: 'red',
};
export interface TimelockState {
export interface ProposalState {
/// Account type
accountType: GovernanceAccountType;
timelockSet: PublicKey;
status: TimelockStateStatus;
proposal: PublicKey;
status: ProposalStateStatus;
totalSigningTokensMinted: BN;
timelockTransactions: PublicKey[];
proposalTransactions: PublicKey[];
name: string;
descLink: string;
votingEndedAt: BN;
@ -171,12 +171,12 @@ export interface TimelockState {
usedTxnSlots: number;
}
const timelockTxns = [];
for (let i = 0; i < TRANSACTION_SLOTS; i++) {
timelockTxns.push(Layout.publicKey('timelockTxn' + i.toString()));
const proposalTxns = [];
for (let i = 0; i < MAX_TRANSACTIONS; i++) {
proposalTxns.push(Layout.publicKey('proposalTxn' + i.toString()));
}
export const TimelockSetLayout: typeof BufferLayout.Structure = BufferLayout.struct(
export const ProposalLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('accountType'),
Layout.publicKey('config'),
@ -198,11 +198,11 @@ export const TimelockSetLayout: typeof BufferLayout.Structure = BufferLayout.str
],
);
export const TimelockStateLayout: typeof BufferLayout.Structure = BufferLayout.struct(
export const ProposalStateLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('accountType'),
Layout.publicKey('timelockSet'),
BufferLayout.u8('timelockStateStatus'),
Layout.publicKey('proposal'),
BufferLayout.u8('proposalStateStatus'),
Layout.uint64('totalSigningTokensMinted'),
BufferLayout.seq(BufferLayout.u8(), DESC_SIZE, 'descLink'),
BufferLayout.seq(BufferLayout.u8(), NAME_SIZE, 'name'),
@ -213,12 +213,12 @@ export const TimelockStateLayout: typeof BufferLayout.Structure = BufferLayout.s
Layout.uint64('deletedAt'),
BufferLayout.u8('executions'),
BufferLayout.u8('usedTxnSlots'),
...timelockTxns,
...proposalTxns,
BufferLayout.seq(BufferLayout.u8(), 300, 'padding'),
],
);
export interface TimelockSet {
export interface Proposal {
/// Account type
accountType: GovernanceAccountType;
@ -270,7 +270,7 @@ export interface TimelockSet {
noVotingDump: PublicKey;
}
export const CustomSingleSignerTimelockTransactionLayout: typeof BufferLayout.Structure = BufferLayout.struct(
export const CustomSingleSignerTransactionLayout: typeof BufferLayout.Structure = BufferLayout.struct(
[
BufferLayout.u8('accountType'),
Layout.uint64('slot'),
@ -281,7 +281,7 @@ export const CustomSingleSignerTimelockTransactionLayout: typeof BufferLayout.St
],
);
export interface TimelockTransaction {
export interface GovernanceTransaction {
/// Account type
accountType: GovernanceAccountType;
@ -293,15 +293,14 @@ export interface TimelockTransaction {
instructionEndIndex: number;
}
export interface CustomSingleSignerTimelockTransaction
extends TimelockTransaction {}
export interface CustomSingleSignerTransaction extends GovernanceTransaction {}
export const TimelockSetParser = (
export const ProposalParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data);
const data = TimelockSetLayout.decode(buffer);
const data = ProposalLayout.decode(buffer);
const details = {
pubkey: pubKey,
account: {
@ -354,16 +353,16 @@ export const GovernanceVotingRecordParser = (
return details;
};
export const TimelockStateParser = (
export const ProposalStateParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data);
const data = TimelockStateLayout.decode(buffer);
const data = ProposalStateLayout.decode(buffer);
const timelockTxns = [];
for (let i = 0; i < TRANSACTION_SLOTS; i++) {
timelockTxns.push(data['timelockTxn' + i.toString()]);
const proposalTxns = [];
for (let i = 0; i < MAX_TRANSACTIONS; i++) {
proposalTxns.push(data['proposalTxn' + i.toString()]);
}
const details = {
@ -373,12 +372,12 @@ export const TimelockStateParser = (
},
info: {
accountType: data.accountType,
timelockSet: data.timelockSet,
status: data.timelockStateStatus,
proposal: data.proposal,
status: data.proposalStateStatus,
totalSigningTokensMinted: data.totalSigningTokensMinted,
descLink: utils.fromUTF8Array(data.descLink).replaceAll('\u0000', ''),
name: utils.fromUTF8Array(data.name).replaceAll('\u0000', ''),
timelockTransactions: timelockTxns,
proposalTransactions: proposalTxns,
votingEndedAt: data.votingEndedAt,
votingBeganAt: data.votingBeganAt,
createdAt: data.createdAt,
@ -392,12 +391,12 @@ export const TimelockStateParser = (
return details;
};
export const CustomSingleSignerTimelockTransactionParser = (
export const CustomSingleSignerTransactionParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data);
const data = CustomSingleSignerTimelockTransactionLayout.decode(buffer);
const data = CustomSingleSignerTransactionLayout.decode(buffer);
const details = {
pubkey: pubKey,
@ -417,12 +416,12 @@ export const CustomSingleSignerTimelockTransactionParser = (
return details;
};
export const TimelockConfigParser = (
export const GovernanceParser = (
pubKey: PublicKey,
info: AccountInfo<Buffer>,
) => {
const buffer = Buffer.from(info.data);
const data = TimelockConfigLayout.decode(buffer);
const data = GovernanceLayout.decode(buffer);
const details = {
pubkey: pubKey,
@ -433,7 +432,7 @@ export const TimelockConfigParser = (
accountType: data.accountType,
voteThreshold: data.voteThreshold,
executionType: data.executionType,
timelockType: data.timelockType,
governanceType: data.governanceType,
votingEntryRule: data.votingEntryRule,
minimumSlotWaitingPeriod: data.minimumSlotWaitingPeriod,
governanceMint: data.governanceMint,

View File

@ -1,22 +1,22 @@
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { CONFIG_NAME_LENGTH, TimelockInstruction } from './timelock';
import { CONFIG_NAME_LENGTH, GovernanceInstruction } from './governance';
import BN from 'bn.js';
import * as Layout from '../utils/layout';
/// 0. `[writable]` Timelock config key. Needs to be set with pubkey set to PDA with seeds of the
/// program account key, governance mint key, council mint key, timelock program account key.
/// 1. `[]` Program account that this config uses
/// 2. `[]` Governance mint that this config uses
/// 3. `[]` Council mint that this config uses [Optional]
export const initTimelockConfigInstruction = (
timelockConfigAccount: PublicKey,
/// 0. `[writable]` Governance account. The account pubkey needs to be set to PDA with the following seeds:
/// 1) 'governance' const prefix, 2) Governed Program account key
/// 1. `[]` Account of the Program governed by this Governance account
/// 2. `[]` Governance mint that this Governance uses
/// 3. `[]` Council mint that this Governance uses [Optional]
export const initGovernanceInstruction = (
governanceAccount: PublicKey,
programAccount: PublicKey,
governanceMint: PublicKey,
voteThreshold: number,
executionType: number,
timelockType: number,
governanceType: number,
votingEntryRule: number,
minimumSlotWaitingPeriod: BN,
timeLimit: BN,
@ -33,7 +33,7 @@ export const initTimelockConfigInstruction = (
BufferLayout.u8('instruction'),
BufferLayout.u8('voteThreshold'),
BufferLayout.u8('executionType'),
BufferLayout.u8('timelockType'),
BufferLayout.u8('governanceType'),
BufferLayout.u8('votingEntryRule'),
Layout.uint64('minimumSlotWaitingPeriod'),
Layout.uint64('timeLimit'),
@ -49,10 +49,10 @@ export const initTimelockConfigInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.InitTimelockConfig,
instruction: GovernanceInstruction.InitGovernance,
voteThreshold,
executionType,
timelockType,
governanceType,
votingEntryRule,
minimumSlotWaitingPeriod,
timeLimit,
@ -62,7 +62,7 @@ export const initTimelockConfigInstruction = (
);
const keys = [
{ pubkey: timelockConfigAccount, isSigner: false, isWritable: true },
{ pubkey: governanceAccount, isSigner: false, isWritable: true },
{ pubkey: programAccount, isSigner: false, isWritable: false },
{ pubkey: governanceMint, isSigner: false, isWritable: false },
];
@ -73,7 +73,7 @@ export const initTimelockConfigInstruction = (
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -5,14 +5,14 @@ import {
} from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { DESC_SIZE, NAME_SIZE, TimelockInstruction } from './timelock';
import { DESC_SIZE, NAME_SIZE, GovernanceInstruction } from './governance';
/// Initializes a new empty Timelocked set of Instructions that will be executed at various slots in the future in draft mode.
/// Initializes a new empty Proposal for Instructions that will be executed at various slots in the future in draft mode.
/// Grants Admin token to caller.
///
/// 0. `[writable]` Uninitialized Timelock state account .
/// 1. `[writable]` Uninitialized Timelock set account .
/// 2. `[writable]` Initialized Timelock config account.
/// 0. `[writable]` Uninitialized Proposal state account .
/// 1. `[writable]` Uninitialized Proposal account .
/// 2. `[writable]` Initialized Governance account.
/// 3. `[writable]` Initialized Signatory Mint account
/// 4. `[writable]` Initialized Admin Mint account
/// 5. `[writable]` Initialized Voting Mint account
@ -27,13 +27,13 @@ import { DESC_SIZE, NAME_SIZE, TimelockInstruction } from './timelock';
/// 14. `[writable]` Initialized No voting dump account
/// 15. `[writable]` Initialized source holding account
/// 16. `[]` Source mint
/// 17. `[]` Timelock minting authority
/// 17. `[]` Governance minting authority (pda with seed of Proposal key)
/// 18. '[]` Token program id
/// 19. `[]` Rent sysvar
export const initTimelockSetInstruction = (
timelockStateAccount: PublicKey,
timelockSetAccount: PublicKey,
timelockConfigAccount: PublicKey,
export const initProposalInstruction = (
proposalStateAccount: PublicKey,
proposalAccount: PublicKey,
governanceAccount: PublicKey,
signatoryMintAccount: PublicKey,
adminMintAccount: PublicKey,
votingMintAccount: PublicKey,
@ -80,7 +80,7 @@ export const initTimelockSetInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.InitTimelockSet,
instruction: GovernanceInstruction.InitProposal,
descLink: descAsBytes,
name: nameAsBytes,
},
@ -88,9 +88,9 @@ export const initTimelockSetInstruction = (
);
const keys = [
{ pubkey: timelockStateAccount, isSigner: true, isWritable: true },
{ pubkey: timelockSetAccount, isSigner: true, isWritable: true },
{ pubkey: timelockConfigAccount, isSigner: false, isWritable: true },
{ pubkey: proposalStateAccount, isSigner: true, isWritable: true },
{ pubkey: proposalAccount, isSigner: true, isWritable: true },
{ pubkey: governanceAccount, isSigner: false, isWritable: true },
{ pubkey: signatoryMintAccount, isSigner: false, isWritable: true },
{ pubkey: adminMintAccount, isSigner: false, isWritable: true },
{ pubkey: votingMintAccount, isSigner: false, isWritable: true },
@ -115,7 +115,7 @@ export const initTimelockSetInstruction = (
];
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -1,7 +1,7 @@
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
/// [Requires Admin token]
/// Removes a signer from the set.
@ -10,16 +10,17 @@ import { TimelockInstruction } from './timelock';
/// 1. `[writable]` Signatory mint account.
/// 2. `[writable]` Admin account.
/// 3. `[writable]` Admin validation account.
/// 4. `[]` Timelock set account.
/// 5. `[]` Transfer authority
/// 6. `[]` Timelock program mint authority
/// 7. '[]` Token program id.
/// 4. `[writable]` Proposal state account.
/// 5. `[]` Proposal account.
/// 6. `[]` Transfer authority
/// 7. `[]` Governance program mint authority (pda of seed with Proposal key)
/// 8. '[]` Token program id.
export const removeSignerInstruction = (
signatoryAccount: PublicKey,
signatoryMintAccount: PublicKey,
adminAccount: PublicKey,
adminValidationAccount: PublicKey,
timelockSetAccount: PublicKey,
proposalAccount: PublicKey,
transferAuthority: PublicKey,
mintAuthority: PublicKey,
): TransactionInstruction => {
@ -31,7 +32,7 @@ export const removeSignerInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.RemoveSigner,
instruction: GovernanceInstruction.RemoveSigner,
},
data,
);
@ -41,14 +42,14 @@ export const removeSignerInstruction = (
{ pubkey: signatoryMintAccount, isSigner: false, isWritable: true },
{ pubkey: adminAccount, isSigner: false, isWritable: true },
{ pubkey: adminValidationAccount, isSigner: false, isWritable: true },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: true },
{ pubkey: proposalAccount, isSigner: false, isWritable: true },
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
];
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -5,25 +5,25 @@ import {
} from '@solana/web3.js';
import { utils } from '@oyster/common';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
/// [Requires Signatory token]
/// Burns signatory token, indicating you approve of moving this Timelock set from Draft state to Voting state.
/// Burns signatory token, indicating you approve of moving this Proposal from Draft state to Voting state.
/// The last Signatory token to be burned moves the state to Voting.
///
/// 0. `[writable]` Timelock state account pub key.
/// 0. `[writable]` Proposal state account pub key.
/// 1. `[writable]` Signatory account
/// 2. `[writable]` Signatory mint account.
/// 3. `[]` Timelock set account pub key.
/// 3. `[]` Proposal account pub key.
/// 4. `[]` Transfer authority
/// 5. `[]` Timelock mint authority
/// 6. `[]` Token program account.
/// 7. `[]` Clock sysvar.
/// 5. `[]` Governance mint authority (pda of seed Proposal key)
/// 7. `[]` Token program account.
/// 8. `[]` Clock sysvar.
export const signInstruction = (
timelockStateAccount: PublicKey,
proposalStateAccount: PublicKey,
signatoryAccount: PublicKey,
signatoryMintAccount: PublicKey,
timelockSetAccount: PublicKey,
proposalAccount: PublicKey,
transferAuthority: PublicKey,
mintAuthority: PublicKey,
): TransactionInstruction => {
@ -35,16 +35,16 @@ export const signInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.Sign,
instruction: GovernanceInstruction.Sign,
},
data,
);
const keys = [
{ pubkey: timelockStateAccount, isSigner: false, isWritable: true },
{ pubkey: proposalStateAccount, isSigner: false, isWritable: true },
{ pubkey: signatoryAccount, isSigner: false, isWritable: true },
{ pubkey: signatoryMintAccount, isSigner: false, isWritable: true },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: false },
{ pubkey: proposalAccount, isSigner: false, isWritable: false },
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
@ -52,7 +52,7 @@ export const signInstruction = (
];
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -7,17 +7,17 @@ import { utils } from '@oyster/common';
import * as Layout from '../utils/layout';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
import BN from 'bn.js';
/// [Requires Voting tokens]
/// Burns voting tokens, indicating you approve and/or disapprove of running this set of transactions. If you tip the vote threshold,
/// then the transactions can begin to be run at their time slots when people click execute.
/// Burns voting tokens, indicating you approve and/or disapprove of running this set of transactions. If you tip the consensus,
/// then the transactions can begin to be run at their time slots when people click execute. You are then given yes and/or no tokens.
///
/// 0. `[writable]` Governance voting record account.
/// Can be uninitialized or initialized(if already used once in this proposal)
/// Must have address with PDA having seed tuple [timelock acct key, proposal key, your voting account key]
/// 1. `[writable]` Timelock state account.
/// Must have address with PDA having seed tuple [Governance acct key, proposal key, your voting account key]
/// 1. `[writable]` Proposal state account.
/// 2. `[writable]` Your Voting account.
/// 3. `[writable]` Your Yes-Voting account.
/// 4. `[writable]` Your No-Voting account.
@ -25,15 +25,15 @@ import BN from 'bn.js';
/// 6. `[writable]` Yes Voting mint account.
/// 7. `[writable]` No Voting mint account.
/// 8. `[]` Source mint account
/// 9. `[]` Timelock set account.
/// 10. `[]` Timelock config account.
/// 9. `[]` Proposal account.
/// 10. `[]` Governance account.
/// 12. `[]` Transfer authority
/// 13. `[]` Timelock program mint authority
/// 13. `[]` Governance program mint authority (pda of seed Proposal key)
/// 14. `[]` Token program account.
/// 15. `[]` Clock sysvar.
export const voteInstruction = (
governanceVotingRecord: PublicKey,
timelockStateAccount: PublicKey,
proposalStateAccount: PublicKey,
votingAccount: PublicKey,
yesVotingAccount: PublicKey,
noVotingAccount: PublicKey,
@ -41,8 +41,8 @@ export const voteInstruction = (
yesVotingMint: PublicKey,
noVotingMint: PublicKey,
sourceMint: PublicKey,
timelockSetAccount: PublicKey,
timelockConfig: PublicKey,
proposalAccount: PublicKey,
governance: PublicKey,
transferAuthority: PublicKey,
mintAuthority: PublicKey,
yesVotingTokenAmount: number,
@ -60,7 +60,7 @@ export const voteInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.Vote,
instruction: GovernanceInstruction.Vote,
yesVotingTokenAmount: new BN(yesVotingTokenAmount),
noVotingTokenAmount: new BN(noVotingTokenAmount),
},
@ -69,7 +69,7 @@ export const voteInstruction = (
const keys = [
{ pubkey: governanceVotingRecord, isSigner: false, isWritable: true },
{ pubkey: timelockStateAccount, isSigner: false, isWritable: true },
{ pubkey: proposalStateAccount, isSigner: false, isWritable: true },
{ pubkey: votingAccount, isSigner: false, isWritable: true },
{ pubkey: yesVotingAccount, isSigner: false, isWritable: true },
{ pubkey: noVotingAccount, isSigner: false, isWritable: true },
@ -77,8 +77,8 @@ export const voteInstruction = (
{ pubkey: yesVotingMint, isSigner: false, isWritable: true },
{ pubkey: noVotingMint, isSigner: false, isWritable: true },
{ pubkey: sourceMint, isSigner: false, isWritable: false },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: false },
{ pubkey: timelockConfig, isSigner: false, isWritable: false },
{ pubkey: proposalAccount, isSigner: false, isWritable: false },
{ pubkey: governance, isSigner: false, isWritable: false },
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
@ -87,7 +87,7 @@ export const voteInstruction = (
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -3,24 +3,27 @@ import { utils } from '@oyster/common';
import * as Layout from '../utils/layout';
import * as BufferLayout from 'buffer-layout';
import { TimelockInstruction } from './timelock';
import { GovernanceInstruction } from './governance';
import BN from 'bn.js';
/// [Requires voting tokens]
/// Withdraws voting tokens.
///
/// 0. `[writable]` Governance voting record account. See Vote docs for more detail.
/// 1. `[writable]` Initialized Voting account from which to remove your voting tokens.
/// 2. `[writable]` Initialized Yes Voting account from which to remove your voting tokens.
/// 3. `[writable]` Initialized No Voting account from which to remove your voting tokens.
/// 4. `[writable]` User token account that you wish your actual tokens to be returned to.
/// 5. `[writable]` Source holding account owned by the timelock that will has the actual tokens in escrow.
/// 6. `[writable]` Initialized Yes Voting dump account owned by timelock set to which to send your voting tokens.
/// 7. `[writable]` Initialized No Voting dump account owned by timelock set to which to send your voting tokens.
/// 5. `[writable]` Source holding account owned by the Governance that will has the actual tokens in escrow.
/// 6. `[writable]` Initialized Yes Voting dump account owned by Proposal to which to send your voting tokens.
/// 7. `[writable]` Initialized No Voting dump account owned by Proposal to which to send your voting tokens.
/// 8. `[writable]` Voting mint account.
/// 9. `[writable]` Yes Voting mint account.
/// 10. `[writable]` No Voting mint account.
/// 11. `[]` Timelock state account.
/// 12. `[]` Timelock set account.
/// 11. `[]` Proposal state account.
/// 12. `[]` Proposal account.
/// 13. `[]` Transfer authority
/// 14. `[]` Timelock program mint authority
/// 14. `[]` Governance program mint authority (pda of seed Proposal key)
/// 15. `[]` Token program account.
export const withdrawVotingTokensInstruction = (
governanceVotingRecord: PublicKey,
@ -34,8 +37,8 @@ export const withdrawVotingTokensInstruction = (
votingMint: PublicKey,
yesVotingMint: PublicKey,
noVotingMint: PublicKey,
timelockStateAccount: PublicKey,
timelockSetAccount: PublicKey,
proposalStateAccount: PublicKey,
proposalAccount: PublicKey,
transferAuthority: PublicKey,
mintAuthority: PublicKey,
votingTokenAmount: number,
@ -51,7 +54,7 @@ export const withdrawVotingTokensInstruction = (
dataLayout.encode(
{
instruction: TimelockInstruction.WithdrawVotingTokens,
instruction: GovernanceInstruction.WithdrawVotingTokens,
votingTokenAmount: new BN(votingTokenAmount),
},
data,
@ -69,8 +72,8 @@ export const withdrawVotingTokensInstruction = (
{ pubkey: votingMint, isSigner: false, isWritable: true },
{ pubkey: yesVotingMint, isSigner: false, isWritable: true },
{ pubkey: noVotingMint, isSigner: false, isWritable: true },
{ pubkey: timelockStateAccount, isSigner: false, isWritable: false },
{ pubkey: timelockSetAccount, isSigner: false, isWritable: false },
{ pubkey: proposalStateAccount, isSigner: false, isWritable: false },
{ pubkey: proposalAccount, isSigner: false, isWritable: false },
{ pubkey: transferAuthority, isSigner: true, isWritable: false },
{ pubkey: mintAuthority, isSigner: false, isWritable: false },
{ pubkey: PROGRAM_IDS.token, isSigner: false, isWritable: false },
@ -78,7 +81,7 @@ export const withdrawVotingTokensInstruction = (
return new TransactionInstruction({
keys,
programId: PROGRAM_IDS.timelock.programId,
programId: PROGRAM_IDS.governance.programId,
data,
});
};

View File

@ -5,7 +5,7 @@ import {
GovernanceVotingRecord,
GovernanceVotingRecordLayout,
GovernanceVotingRecordParser,
} from '../models/timelock';
} from '../models/governance';
const MAX_LOOKUPS = 5000;
export async function getGovernanceVotingRecords(
@ -25,7 +25,7 @@ export async function getGovernanceVotingRecords(
id: 1,
method: 'getProgramAccounts',
params: [
PROGRAM_IDS.timelock.programId.toBase58(),
PROGRAM_IDS.governance.programId.toBase58(),
{
commitment: 'single',
filters: [

View File

@ -6,7 +6,7 @@ import {
PublicKey,
Message,
} from '@solana/web3.js';
import { GOVERNANCE_AUTHORITY_SEED, TimelockSet } from '../models/timelock';
import { GOVERNANCE_AUTHORITY_SEED, Proposal } from '../models/governance';
export async function serializeInstruction({
connection,
instr,
@ -14,7 +14,7 @@ export async function serializeInstruction({
}: {
connection: Connection;
instr: TransactionInstruction;
proposal: ParsedAccount<TimelockSet>;
proposal: ParsedAccount<Proposal>;
}): Promise<{ base64: string; byteArray: Uint8Array }> {
const PROGRAM_IDS = utils.programIds();
let instructionTransaction = new Transaction();
@ -24,7 +24,7 @@ export async function serializeInstruction({
).blockhash;
const [authority] = await PublicKey.findProgramAddress(
[Buffer.from(GOVERNANCE_AUTHORITY_SEED), proposal.pubkey.toBuffer()],
PROGRAM_IDS.timelock.programId,
PROGRAM_IDS.governance.programId,
);
instructionTransaction.setSigners(authority);
const msg: Message = instructionTransaction.compileMessage();

View File

@ -1,76 +1,6 @@
import {
Account,
Connection,
Message,
PublicKey,
TransactionInstruction,
} from '@solana/web3.js';
import { contexts, utils, ParsedAccount } from '@oyster/common';
import { Message, PublicKey } from '@solana/web3.js';
import {
TimelockSet,
TimelockState,
TimelockTransaction,
} from '../models/timelock';
import { executeInstruction } from '../models/execute';
import { LABELS } from '../constants';
const { sendTransaction } = contexts.Connection;
const { notify } = utils;
export const execute = async (
connection: Connection,
wallet: any,
proposal: ParsedAccount<TimelockSet>,
state: ParsedAccount<TimelockState>,
transaction: ParsedAccount<TimelockTransaction>,
) => {
let signers: Account[] = [];
let instructions: TransactionInstruction[] = [];
const actualMessage = decodeBufferIntoMessage(transaction.info.instruction);
const accountInfos = getAccountInfos(actualMessage);
instructions.push(
executeInstruction(
transaction.pubkey,
state.pubkey,
proposal.pubkey,
actualMessage.accountKeys[actualMessage.instructions[0].programIdIndex],
proposal.info.config,
accountInfos,
),
);
notify({
message: LABELS.EXECUTING,
description: LABELS.PLEASE_WAIT,
type: 'warn',
});
try {
let tx = await sendTransaction(
connection,
wallet,
instructions,
signers,
true,
);
notify({
message: LABELS.EXECUTED,
type: 'success',
description: LABELS.TRANSACTION + ` ${tx}`,
});
} catch (ex) {
console.error(ex);
throw new Error();
}
};
function decodeBufferIntoMessage(instruction: number[]): Message {
return Message.from(instruction);
}
function getAccountInfos(
export function getMessageAccountInfos(
actualMessage: Message,
): { pubkey: PublicKey; isSigner: boolean; isWritable: boolean }[] {
console.log(actualMessage);

View File

@ -4,7 +4,7 @@ import { GUTTER } from '../../constants';
import { contexts, hooks, ParsedAccount } from '@oyster/common';
import './style.less';
import { useProposals } from '../../contexts/proposals';
import { TimelockSet } from '../../models/timelock';
import { Proposal } from '../../models/governance';
import { Connection } from '@solana/web3.js';
import { WalletAdapter } from '@solana/wallet-base';
const { useWallet } = contexts.Wallet;
@ -38,7 +38,7 @@ function InnerDummyView({
}: {
connection: Connection;
wallet: WalletAdapter;
proposal: ParsedAccount<TimelockSet>;
proposal: ParsedAccount<Proposal>;
}) {
const sigAccount = useAccountByMint(proposal.info.signatoryMint);
if (!sigAccount) return <Spin />;

View File

@ -11,10 +11,10 @@ import {
} from '@oyster/common';
import {
ExecutionType,
TimelockConfig,
TimelockType,
Governance,
GovernanceType,
VotingEntryRule,
} from '../../models/timelock';
} from '../../models/governance';
import { PublicKey } from '@solana/web3.js';
import { Table } from 'antd';
import MintSourceTokens from '../../components/Proposal/MintSourceTokens';
@ -40,9 +40,9 @@ const columns = [
},
{
title: LABELS.PROPOSAL_TYPE,
dataIndex: 'timelockType',
key: 'timelockType',
render: (number: number) => <span>{TimelockType[number]}</span>,
dataIndex: 'governanceType',
key: 'governanceType',
render: (number: number) => <span>{GovernanceType[number]}</span>,
},
{
title: LABELS.VOTING_ENTRY_RULES,
@ -75,7 +75,7 @@ const columns = [
render: (key: PublicKey) => <span>{key?.toBase58()}</span>,
},
{
title: LABELS.PROGRAM,
title: LABELS.PROGRAM_ID,
dataIndex: 'program',
key: 'program',
render: (key: PublicKey) => <span>{key.toBase58()}</span>,
@ -85,11 +85,11 @@ const columns = [
title: LABELS.ACTIONS,
dataIndex: 'config',
key: 'config',
render: (config: ParsedAccount<TimelockConfig>) => (
render: (config: ParsedAccount<Governance>) => (
<>
<MintSourceTokens timelockConfig={config} useGovernance={true} />
<MintSourceTokens governance={config} useGovernance={true} />
{config.info.councilMint && (
<MintSourceTokens timelockConfig={config} useGovernance={false} />
<MintSourceTokens governance={config} useGovernance={false} />
)}
</>
),
@ -133,7 +133,7 @@ export const GovernanceDashboard = () => {
})),
);
});
}, [configs.length, myTokenAccts.join(',')]);
}, [configs.length, myTokenAccts.join(',')]); //eslint-disable-line
return <Table columns={columns} dataSource={data} />;
};

View File

@ -5,9 +5,9 @@ import { PublicKey } from '@solana/web3.js';
import {
CONFIG_NAME_LENGTH,
ExecutionType,
TimelockType,
GovernanceType,
VotingEntryRule,
} from '../../models/timelock';
} from '../../models/governance';
import { LABELS } from '../../constants';
import { contexts, utils, tryParseKey } from '@oyster/common';
import { registerProgramGovernance } from '../../actions/registerProgramGovernance';
@ -68,7 +68,7 @@ export function NewForm({
const wallet = useWallet();
const connection = useConnection();
const onFinish = async (values: {
timelockType: TimelockType;
governanceType: GovernanceType;
executionType: ExecutionType;
voteThreshold: number;
votingEntryRule: VotingEntryRule;
@ -119,8 +119,8 @@ export function NewForm({
return;
}
const uninitializedConfig = {
timelockType: values.timelockType,
const uninitializedGovernance = {
governanceType: values.governanceType,
executionType: values.executionType,
voteThreshold: values.voteThreshold,
votingEntryRule: values.votingEntryRule,
@ -140,7 +140,7 @@ export function NewForm({
const newConfig = await registerProgramGovernance(
connection,
wallet.wallet,
uninitializedConfig,
uninitializedGovernance,
councilVisible,
);
handleOk(newConfig);
@ -158,7 +158,7 @@ export function NewForm({
</Form.Item>
<Form.Item
name="program"
label={LABELS.PROGRAM}
label={LABELS.PROGRAM_ID}
rules={[{ required: true }]}
>
<Input />
@ -215,15 +215,13 @@ export function NewForm({
</Select>
</Form.Item>
<Form.Item
name="timelockType"
name="governanceType"
label={LABELS.PROPOSAL_TYPE}
rules={[{ required: true }]}
initialValue={TimelockType.CustomSingleSignerV1}
initialValue={GovernanceType.Governance}
>
<Select placeholder={LABELS.SELECT_PROPOSAL_TYPE}>
<Option value={TimelockType.CustomSingleSignerV1}>
Single Signer
</Option>
<Option value={GovernanceType.Governance}>Single Signer</Option>
</Select>
</Form.Item>
<Form.Item

View File

@ -6,7 +6,7 @@ import { TokenIcon, useWallet } from '@oyster/common';
import { Background } from './../../components/Background';
import { useHistory } from 'react-router-dom';
import { RegisterGovernanceMenuItem } from '../governance/register';
import { TimelockStateStatus } from '../../models/timelock';
import { ProposalStateStatus } from '../../models/governance';
export const HomeView = () => {
const history = useHistory();
@ -26,8 +26,8 @@ export const HomeView = () => {
if (proposal.info.config.toBase58() === configKey) {
acc.active =
acc.active +
(state.info.status === TimelockStateStatus.Voting ||
state.info.status === TimelockStateStatus.Draft
(state.info.status === ProposalStateStatus.Voting ||
state.info.status === ProposalStateStatus.Draft
? 1
: 0);
@ -56,7 +56,7 @@ export const HomeView = () => {
});
});
return newListData;
}, [configs, proposals]);
}, [configs, proposals, states]);
return (
<>

View File

@ -1,15 +1,15 @@
import { Card, Col, Grid, Row, Spin, Statistic, Tabs } from 'antd';
import { Card, Col, Row, Spin, Statistic, Tabs } from 'antd';
import React, { useMemo, useState } from 'react';
import { LABELS } from '../../constants';
import { ParsedAccount, TokenIcon } from '@oyster/common';
import {
INSTRUCTION_LIMIT,
TimelockConfig,
TimelockSet,
TimelockState,
TimelockStateStatus,
TimelockTransaction,
} from '../../models/timelock';
Governance,
Proposal,
ProposalState,
ProposalStateStatus,
GovernanceTransaction,
} from '../../models/governance';
import { useParams } from 'react-router-dom';
import ReactMarkdown from 'react-markdown';
import { useProposals } from '../../contexts/proposals';
@ -30,11 +30,12 @@ import { VoterBubbleGraph } from '../../components/Proposal/VoterBubbleGraph';
import { VoterTable } from '../../components/Proposal/VoterTable';
const { TabPane } = Tabs;
// eslint-disable-next-line
export const urlRegex = /[-a-zA-Z0-9@:%._\+~#=]{1,256}\.[a-zA-Z0-9()]{1,6}\b([-a-zA-Z0-9()@:%_\+.~#?&//=]*)/;
const { useMint } = contexts.Accounts;
const { useConnectionConfig } = contexts.Connection;
const { useAccountByMint } = hooks;
const { useBreakpoint } = Grid;
//const { useBreakpoint } = Grid;
export enum VoteType {
Undecided = 'Undecided',
@ -46,8 +47,8 @@ export const ProposalView = () => {
const context = useProposals();
const { id } = useParams<{ id: string }>();
const proposal = context.proposals[id];
const timelockConfig = context.configs[proposal?.info.config.toBase58()];
const timelockState = context.states[proposal?.info.state.toBase58()];
const governance = context.configs[proposal?.info.config.toBase58()];
const proposalState = context.states[proposal?.info.state.toBase58()];
const { endpoint } = useConnectionConfig();
const sigMint = useMint(proposal?.info.signatoryMint);
const votingMint = useMint(proposal?.info.votingMint);
@ -68,8 +69,8 @@ export const ProposalView = () => {
noVotingMint ? (
<InnerProposalView
proposal={proposal}
timelockState={timelockState}
timelockConfig={timelockConfig}
proposalState={proposalState}
governance={governance}
sourceMint={sourceMint}
votingMint={votingMint}
yesVotingMint={yesVotingMint}
@ -93,7 +94,7 @@ function useLoadGist({
setMsg,
setContent,
isGist,
timelockState,
proposalState,
}: {
loading: boolean;
setLoading: (b: boolean) => void;
@ -101,11 +102,11 @@ function useLoadGist({
setFailed: (b: boolean) => void;
setContent: (b: string) => void;
isGist: boolean;
timelockState: ParsedAccount<TimelockState>;
proposalState: ParsedAccount<ProposalState>;
}) {
useMemo(() => {
if (loading) {
let toFetch = timelockState.info.descLink;
let toFetch = proposalState.info.descLink;
const pieces = toFetch.match(urlRegex);
if (isGist && pieces) {
const justIdWithoutUser = pieces[1].split('/')[2];
@ -134,7 +135,7 @@ function useLoadGist({
setLoading(false);
});
}
}, [loading]);
}, [loading]); //eslint-disable-line
}
interface PartialGovernanceRecord {
info: { yesCount: BN; noCount: BN; undecidedCount: BN };
@ -155,6 +156,7 @@ function voterDisplayData(
title: key,
group: label,
value: amount,
key: key,
});
const undecidedData = [
@ -198,51 +200,52 @@ function voterDisplayData(
const data = [...undecidedData, ...yesData, ...noData].sort(
(a, b) => b.value - a.value,
);
return data;
}
function InnerProposalView({
proposal,
timelockState,
proposalState,
sigMint,
votingMint,
yesVotingMint,
noVotingMint,
instructions,
timelockConfig,
governance,
sourceMint,
votingDisplayData,
endpoint,
}: {
proposal: ParsedAccount<TimelockSet>;
timelockConfig: ParsedAccount<TimelockConfig>;
timelockState: ParsedAccount<TimelockState>;
proposal: ParsedAccount<Proposal>;
governance: ParsedAccount<Governance>;
proposalState: ParsedAccount<ProposalState>;
sigMint: MintInfo;
votingMint: MintInfo;
yesVotingMint: MintInfo;
noVotingMint: MintInfo;
sourceMint: MintInfo;
instructions: Record<string, ParsedAccount<TimelockTransaction>>;
instructions: Record<string, ParsedAccount<GovernanceTransaction>>;
votingDisplayData: Array<VoterDisplayData>;
endpoint: string;
}) {
const sigAccount = useAccountByMint(proposal.info.signatoryMint);
const adminAccount = useAccountByMint(proposal.info.adminMint);
const instructionsForProposal: ParsedAccount<TimelockTransaction>[] = timelockState.info.timelockTransactions
const instructionsForProposal: ParsedAccount<GovernanceTransaction>[] = proposalState.info.proposalTransactions
.map(k => instructions[k.toBase58()])
.filter(k => k);
const isUrl = !!timelockState.info.descLink.match(urlRegex);
const isUrl = !!proposalState.info.descLink.match(urlRegex);
const isGist =
!!timelockState.info.descLink.match(/gist/i) &&
!!timelockState.info.descLink.match(/github/i);
const [content, setContent] = useState(timelockState.info.descLink);
!!proposalState.info.descLink.match(/gist/i) &&
!!proposalState.info.descLink.match(/github/i);
const [content, setContent] = useState(proposalState.info.descLink);
const [loading, setLoading] = useState(isUrl);
const [failed, setFailed] = useState(false);
const [msg, setMsg] = useState('');
const [width, setWidth] = useState<number>();
const [height, setHeight] = useState<number>();
const breakpoint = useBreakpoint();
// const breakpoint = useBreakpoint();
useLoadGist({
loading,
@ -251,7 +254,7 @@ function InnerProposalView({
setMsg,
setContent,
isGist,
timelockState,
proposalState: proposalState,
});
return (
@ -265,8 +268,8 @@ function InnerProposalView({
size={60}
/>
<Col>
<h1>{timelockState.info.name}</h1>
<StateBadge state={timelockState} />
<h1>{proposalState.info.name}</h1>
<StateBadge state={proposalState} />
</Col>
</Row>
</Col>
@ -274,36 +277,32 @@ function InnerProposalView({
<div className="proposal-actions">
{adminAccount &&
adminAccount.info.amount.toNumber() === 1 &&
timelockState.info.status === TimelockStateStatus.Draft && (
<AddSigners proposal={proposal} state={timelockState} />
proposalState.info.status === ProposalStateStatus.Draft && (
<AddSigners proposal={proposal} state={proposalState} />
)}
{sigAccount &&
sigAccount.info.amount.toNumber() === 1 &&
timelockState.info.status === TimelockStateStatus.Draft && (
<SignButton proposal={proposal} state={timelockState} />
proposalState.info.status === ProposalStateStatus.Draft && (
<SignButton proposal={proposal} state={proposalState} />
)}
<MintSourceTokens
timelockConfig={timelockConfig}
governance={governance}
useGovernance={
proposal.info.sourceMint.toBase58() ===
timelockConfig.info.governanceMint.toBase58()
governance.info.governanceMint.toBase58()
}
/>
<WithdrawVote
timelockConfig={timelockConfig}
proposal={proposal}
state={timelockState}
/>
<WithdrawVote proposal={proposal} state={proposalState} />
<Vote
timelockConfig={timelockConfig}
governance={governance}
proposal={proposal}
state={timelockState}
state={proposalState}
yeahVote={true}
/>
<Vote
timelockConfig={timelockConfig}
governance={governance}
proposal={proposal}
state={timelockState}
state={proposalState}
yeahVote={false}
/>
</div>
@ -367,10 +366,10 @@ function InnerProposalView({
<Statistic
title={LABELS.SIG_GIVEN}
value={
timelockState.info.totalSigningTokensMinted.toNumber() -
proposalState.info.totalSigningTokensMinted.toNumber() -
sigMint.supply.toNumber()
}
suffix={`/ ${timelockState.info.totalSigningTokensMinted.toNumber()}`}
suffix={`/ ${proposalState.info.totalSigningTokensMinted.toNumber()}`}
/>
</Card>
</Col>
@ -388,7 +387,7 @@ function InnerProposalView({
<Statistic
valueStyle={{ color: 'green' }}
title={LABELS.VOTES_REQUIRED}
value={getVotesRequired(timelockConfig, sourceMint)}
value={getVotesRequired(governance, sourceMint)}
/>
</Card>
</Col>
@ -409,7 +408,7 @@ function InnerProposalView({
<p>
{LABELS.DESCRIPTION}:{' '}
<a
href={timelockState.info.descLink}
href={proposalState.info.descLink}
target="_blank"
rel="noopener noreferrer"
>
@ -436,17 +435,17 @@ function InnerProposalView({
proposal={proposal}
position={position + 1}
instruction={instruction}
state={timelockState}
state={proposalState}
/>
</Col>
))}
{instructionsForProposal.length < INSTRUCTION_LIMIT &&
timelockState.info.status === TimelockStateStatus.Draft && (
proposalState.info.status === ProposalStateStatus.Draft && (
<Col xs={24} sm={24} md={12} lg={8}>
<NewInstructionCard
proposal={proposal}
state={timelockState}
config={timelockConfig}
state={proposalState}
config={governance}
position={instructionsForProposal.length}
/>
</Col>
@ -462,13 +461,12 @@ function InnerProposalView({
}
function getVotesRequired(
timelockConfig: ParsedAccount<TimelockConfig>,
governance: ParsedAccount<Governance>,
sourceMint: MintInfo,
): number {
return timelockConfig.info.voteThreshold === 100
return governance.info.voteThreshold === 100
? sourceMint.supply.toNumber()
: Math.ceil(
(timelockConfig.info.voteThreshold / 100) *
sourceMint.supply.toNumber(),
(governance.info.voteThreshold / 100) * sourceMint.supply.toNumber(),
);
}

View File

@ -2,7 +2,7 @@ import React, { useState } from 'react';
import { Button, ButtonProps, Modal, Radio } from 'antd';
import { Form, Input, Select } from 'antd';
import { Account } from '@solana/web3.js';
import { DESC_SIZE, NAME_SIZE } from '../../models/timelock';
import { DESC_SIZE, NAME_SIZE } from '../../models/governance';
import { LABELS } from '../../constants';
import { contexts, utils } from '@oyster/common';
import { createProposal } from '../../actions/createProposal';
@ -74,9 +74,9 @@ export function NewForm({
name: string;
proposalMintType: string;
description: string;
timelockConfigKey: string;
governanceKey: string;
}) => {
const config = context.configs[values.timelockConfigKey];
const config = context.configs[values.governanceKey];
if (
values.proposalMintType === ProposalMintType.Council &&
@ -132,7 +132,7 @@ export function NewForm({
<Input maxLength={DESC_SIZE} placeholder={LABELS.GIST_PLACEHOLDER} />
</Form.Item>
<Form.Item
name="timelockConfigKey"
name="governanceKey"
label={LABELS.CONFIG}
rules={[{ required: true }]}
>

View File

@ -45,7 +45,7 @@ export const ProposalsView = () => {
});
});
return newListData;
}, [proposals]);
}, [proposals, id, mint, states]);
return (
<Row

View File

@ -3,6 +3,6 @@ import React from 'react';
if (process.env.NODE_ENV === 'development') {
const whyDidYouRender = require('@welldone-software/why-did-you-render');
whyDidYouRender(React, {
trackAllPureComponents: true,
trackAllPureComponents: false,
});
}