Governance: Create mint instruction (#121)

* feat: setup CreateMintInstruction

* fix: change instruction and enum layout to avoid braking changes
This commit is contained in:
Sebastian Bor 2021-06-25 22:09:00 +01:00 committed by GitHub
parent 3db71073f7
commit ac8d8e21c0
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
12 changed files with 205 additions and 50 deletions

View File

@ -5,6 +5,7 @@ import { GovernanceType } from '../models/enums';
import { GovernanceConfig } from '../models/accounts';
import { withCreateProgramGovernance } from '../models/withCreateProgramGovernance';
import { sendTransactionWithNotifications } from '../tools/transactions';
import { withCreateMintGovernance } from '../models/withCreateMintGovernance';
export const registerGovernance = async (
connection: Connection,
@ -12,34 +13,55 @@ export const registerGovernance = async (
governanceType: GovernanceType,
realm: PublicKey,
config: GovernanceConfig,
transferUpgradeAuthority?: boolean,
transferAuthority?: boolean,
): Promise<PublicKey> => {
let instructions: TransactionInstruction[] = [];
let governanceAddress;
if (governanceType === GovernanceType.Account) {
governanceAddress = (
await withCreateAccountGovernance(
instructions,
realm,
config,
wallet.publicKey,
)
).governanceAddress;
} else if (governanceType === GovernanceType.Program) {
governanceAddress = (
await withCreateProgramGovernance(
instructions,
realm,
config,
transferUpgradeAuthority!,
wallet.publicKey,
wallet.publicKey,
)
).governanceAddress;
} else {
throw new Error(`Governance type ${governanceType} is not supported yet.`);
switch (governanceType) {
case GovernanceType.Account: {
governanceAddress = (
await withCreateAccountGovernance(
instructions,
realm,
config,
wallet.publicKey,
)
).governanceAddress;
break;
}
case GovernanceType.Program: {
governanceAddress = (
await withCreateProgramGovernance(
instructions,
realm,
config,
transferAuthority!,
wallet.publicKey,
wallet.publicKey,
)
).governanceAddress;
break;
}
case GovernanceType.Mint: {
governanceAddress = (
await withCreateMintGovernance(
instructions,
realm,
config,
transferAuthority!,
wallet.publicKey,
wallet.publicKey,
)
).governanceAddress;
break;
}
default: {
throw new Error(
`Governance type ${governanceType} is not supported yet.`,
);
}
}
await sendTransactionWithNotifications(

View File

@ -109,11 +109,13 @@ export const LABELS = {
REGISTER_GOVERNANCE: 'Register Governance',
GOVERNANCE_OVER: 'governance over',
PROGRAM: 'Program',
MINT: 'Mint',
REGISTER: 'Register',
REGISTERING: 'Registering',
PROGRAM_ID_LABEL: 'program id',
MINT_ADDRESS_LABEL: 'mint address',
ACCOUNT_ADDRESS: 'account address',
MIN_TOKENS_TO_CREATE_PROPOSAL: 'min tokens to create proposal',
@ -121,6 +123,7 @@ export const LABELS = {
MAX_VOTING_TIME: 'max voting time (slots)',
TRANSFER_UPGRADE_AUTHORITY: 'transfer upgrade authority',
TRANSFER_MINT_AUTHORITY: 'transfer mint authority',
PROGRAM_ID: 'Program ID',
INSTRUCTION: 'Instruction',

View File

@ -15,6 +15,7 @@ export enum GovernanceAccountType {
SignatoryRecord = 6,
VoteRecord = 7,
ProposalInstruction = 8,
MintGovernance = 9,
}
export interface GovernanceAccount {
@ -48,6 +49,7 @@ export function getAccountTypes(accountClass: GovernanceAccountClass) {
return [
GovernanceAccountType.AccountGovernance,
GovernanceAccountType.ProgramGovernance,
GovernanceAccountType.MintGovernance,
];
default:
throw Error(`${accountClass} account is not supported`);
@ -108,6 +110,10 @@ export class Governance {
return this.accountType === GovernanceAccountType.ProgramGovernance;
}
isMintGovernance() {
return this.accountType === GovernanceAccountType.MintGovernance;
}
constructor(args: {
accountType: number;
config: GovernanceConfig;

View File

@ -6,4 +6,5 @@ export enum GoverningTokenType {
export enum GovernanceType {
Account,
Program,
Mint,
}

View File

@ -40,15 +40,21 @@ export const GovernanceError: Record<number, string> = [
'Proposal voting time expired', // ProposalVotingTimeExpired
'Invalid Signatory Mint', // InvalidSignatoryMint
'Invalid account owner', // InvalidAccountOwner
"Account doesn't exist", // AccountDoesNotExist
'Invalid Account type', // InvalidAccountType
'Proposal does not belong to the given Governance', // InvalidGovernanceForProposal
'Proposal does not belong to given Governing Mint', // InvalidGoverningMintForProposal
'Current mint authority must sign transaction', // MintAuthorityMustSign
'Invalid mint authority', // InvalidMintAuthority
'Mint has no authority', // MintHasNoAuthority
'Invalid Token account owner', // SplTokenAccountWithInvalidOwner
'Invalid Mint account owner', // SplTokenMintWithInvalidOwner
'Token Account is not initialized', // SplTokenAccountNotInitialized
"Token Account doesn't exist", // SplTokenAccountDoesNotExist
'Token account data is invalid', // SplTokenInvalidTokenAccountData
'Token mint account data is invalid', // SplTokenInvalidMintAccountData
'Token Mint account is not initialized', // SplTokenMintNotInitialized
"Token Mint account doesn't exist", // SplTokenMintDoesNotExist
'Invalid ProgramData account address', // InvalidProgramDataAccountAddress
'Invalid ProgramData account Data', // InvalidProgramDataAccountData
"Provided upgrade authority doesn't match current program upgrade authority", // InvalidUpgradeAuthority

View File

@ -9,6 +9,7 @@ export enum GovernanceInstruction {
SetGovernanceDelegate = 3, // --
CreateAccountGovernance = 4,
CreateProgramGovernance = 5,
CreateProposal = 6,
AddSignatory = 7,
RemoveSignatory = 8,
@ -21,6 +22,8 @@ export enum GovernanceInstruction {
FinalizeVote = 14,
RelinquishVote = 15,
ExecuteInstruction = 16,
CreateMintGovernance = 17,
}
export class CreateRealmArgs {
@ -67,6 +70,21 @@ export class CreateProgramGovernanceArgs {
}
}
export class CreateMintGovernanceArgs {
instruction: GovernanceInstruction =
GovernanceInstruction.CreateMintGovernance;
config: GovernanceConfig;
transferMintAuthority: boolean;
constructor(args: {
config: GovernanceConfig;
transferMintAuthority: boolean;
}) {
this.config = args.config;
this.transferMintAuthority = !!args.transferMintAuthority;
}
}
export class CreateProposalArgs {
instruction: GovernanceInstruction = GovernanceInstruction.CreateProposal;
name: string;

View File

@ -11,6 +11,7 @@ import {
CancelProposalArgs,
CastVoteArgs,
CreateAccountGovernanceArgs,
CreateMintGovernanceArgs,
CreateProgramGovernanceArgs,
CreateProposalArgs,
CreateRealmArgs,
@ -126,6 +127,17 @@ export const GOVERNANCE_SCHEMA = new Map<any, any>([
],
},
],
[
CreateMintGovernanceArgs,
{
kind: 'struct',
fields: [
['instruction', 'u8'],
['config', GovernanceConfig],
['transferMintAuthority', 'u8'],
],
},
],
[
CreateProposalArgs,
{

View File

@ -0,0 +1,89 @@
import { utils } from '@oyster/common';
import {
PublicKey,
SYSVAR_RENT_PUBKEY,
TransactionInstruction,
} from '@solana/web3.js';
import { GOVERNANCE_SCHEMA } from './serialisation';
import { serialize } from 'borsh';
import { GovernanceConfig } from './accounts';
import { CreateMintGovernanceArgs } from './instructions';
export const withCreateMintGovernance = async (
instructions: TransactionInstruction[],
realm: PublicKey,
config: GovernanceConfig,
transferMintAuthority: boolean,
mintAuthority: PublicKey,
payer: PublicKey,
): Promise<{ governanceAddress: PublicKey }> => {
const PROGRAM_IDS = utils.programIds();
const args = new CreateMintGovernanceArgs({
config,
transferMintAuthority,
});
const data = Buffer.from(serialize(GOVERNANCE_SCHEMA, args));
const [mintGovernanceAddress] = await PublicKey.findProgramAddress(
[
Buffer.from('mint-governance'),
realm.toBuffer(),
config.governedAccount.toBuffer(),
],
PROGRAM_IDS.governance.programId,
);
const keys = [
{
pubkey: realm,
isWritable: false,
isSigner: false,
},
{
pubkey: mintGovernanceAddress,
isWritable: true,
isSigner: false,
},
{
pubkey: config.governedAccount,
isWritable: true,
isSigner: false,
},
{
pubkey: mintAuthority,
isWritable: false,
isSigner: true,
},
{
pubkey: payer,
isWritable: false,
isSigner: true,
},
{
pubkey: PROGRAM_IDS.token,
isWritable: false,
isSigner: false,
},
{
pubkey: PROGRAM_IDS.system,
isWritable: false,
isSigner: false,
},
{
pubkey: SYSVAR_RENT_PUBKEY,
isWritable: false,
isSigner: false,
},
];
instructions.push(
new TransactionInstruction({
keys,
programId: PROGRAM_IDS.governance.programId,
data,
}),
);
return { governanceAddress: mintGovernanceAddress };
};

View File

@ -6,12 +6,7 @@ import { useGovernance, useProposalsByGovernance } from '../../hooks/apiHooks';
import './style.less'; // Don't remove this line, it will break dark mode if you do due to weird transpiling conditions
import { StateBadge } from '../proposal/components/StateBadge';
import { useHistory } from 'react-router-dom';
import {
ExplorerLink,
TokenIcon,
useConnectionConfig,
useMint,
} from '@oyster/common';
import { ExplorerLink, TokenIcon, useConnectionConfig } from '@oyster/common';
import { AddNewProposal } from './NewProposal';
import { useKeyParam } from '../../hooks/useKeyParam';
import { Proposal, ProposalState } from '../../models/accounts';
@ -39,8 +34,6 @@ export const GovernanceView = () => {
const mint = communityTokenMint?.toBase58() || '';
// We don't support MintGovernance account yet, but we can check the governed account type here
const governedMint = useMint(governance?.info.config.governedAccount);
const color = governance?.info.isProgramGovernance() ? 'green' : 'gray';
const proposalItems = useMemo(() => {
@ -78,7 +71,7 @@ export const GovernanceView = () => {
>
<Col flex="auto" xxl={15} xs={24} className="proposals-container">
<div className="proposals-header">
{governedMint ? (
{governance?.info.isMintGovernance() ? (
<TokenIcon
mintAddress={governance?.info.config.governedAccount}
size={60}

View File

@ -1,5 +1,5 @@
import { PlusCircleOutlined } from '@ant-design/icons';
import { ExplorerLink, ParsedAccount, useMint, utils } from '@oyster/common';
import { ExplorerLink, ParsedAccount, utils } from '@oyster/common';
import { Token } from '@solana/spl-token';
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
import {
@ -33,9 +33,8 @@ const InstructionInput = ({
const [instruction, setInstruction] = useState('');
const [form] = Form.useForm();
// We don't support MintGovernance account yet, but we can check the governed account type here
const mint = useMint(governance.info.config.governedAccount);
const creatorsEnabled = mint || governance.info.isProgramGovernance();
const creatorsEnabled =
governance.info.isMintGovernance() || governance.info.isProgramGovernance();
const updateInstruction = (instruction: string) => {
setInstruction(instruction);
@ -86,7 +85,7 @@ const InstructionInput = ({
governance={governance}
></UpgradeProgramForm>
)}
{mint && (
{governance.info.isMintGovernance() && (
<MintToForm
form={form}
onCreateInstruction={onCreateInstruction}

View File

@ -1,4 +1,4 @@
import { ParsedAccount, TokenIcon, useMint } from '@oyster/common';
import { ParsedAccount, TokenIcon } from '@oyster/common';
import { Avatar, Badge } from 'antd';
import React from 'react';
import { Governance, ProposalState } from '../../models/accounts';
@ -10,8 +10,6 @@ export function GovernanceBadge({
}: {
governance: ParsedAccount<Governance>;
}) {
// We don't support MintGovernance account yet, but we can check the governed account type here
const governedMint = useMint(governance.info.config.governedAccount);
const proposals = useProposalsByGovernance(governance?.pubkey);
const color = governance.info.isProgramGovernance() ? 'green' : 'gray';
@ -22,7 +20,7 @@ export function GovernanceBadge({
}
>
<div style={{ width: 55, height: 45 }}>
{governedMint ? (
{governance.info.isMintGovernance() ? (
<TokenIcon
mintAddress={governance.info.config.governedAccount}
size={40}

View File

@ -38,7 +38,7 @@ export function RegisterGovernance({
maxVotingTime: number;
yesVoteThresholdPercentage: number;
governedAccountAddress: string;
transferUpgradeAuthority: boolean;
transferAuthority: boolean;
}) => {
const config = new GovernanceConfig({
realm: realmKey,
@ -54,7 +54,7 @@ export function RegisterGovernance({
values.governanceType,
realmKey,
config,
values.transferUpgradeAuthority,
values.transferAuthority,
);
};
@ -77,7 +77,7 @@ export function RegisterGovernance({
onComplete={onComplete}
initialValues={{
governanceType: GovernanceType.Account,
transferUpgradeAuthority: true,
transferAuthority: true,
}}
>
<Form.Item label={LABELS.GOVERNANCE_OVER} name="governanceType">
@ -88,22 +88,30 @@ export function RegisterGovernance({
<Radio.Button value={GovernanceType.Program}>
{LABELS.PROGRAM}
</Radio.Button>
<Radio.Button value={GovernanceType.Mint}>{LABELS.MINT}</Radio.Button>
</Radio.Group>
</Form.Item>
<AccountFormItem
name="governedAccountAddress"
label={
governanceType === GovernanceType.Account
? LABELS.ACCOUNT_ADDRESS
: LABELS.PROGRAM_ID_LABEL
governanceType === GovernanceType.Program
? LABELS.PROGRAM_ID_LABEL
: governanceType === GovernanceType.Mint
? LABELS.MINT_ADDRESS_LABEL
: LABELS.ACCOUNT_ADDRESS
}
></AccountFormItem>
{governanceType === GovernanceType.Program && (
{(governanceType === GovernanceType.Program ||
governanceType === GovernanceType.Mint) && (
<Form.Item
name="transferUpgradeAuthority"
label={LABELS.TRANSFER_UPGRADE_AUTHORITY}
name="transferAuthority"
label={
governanceType === GovernanceType.Program
? LABELS.TRANSFER_UPGRADE_AUTHORITY
: LABELS.TRANSFER_MINT_AUTHORITY
}
valuePropName="checked"
>
<Checkbox></Checkbox>