mirror of https://github.com/certusone/oyster.git
At great expense, we have upgradeable programs working. Still eneds a lot of beautifciation.
This commit is contained in:
parent
9ce5b9644c
commit
04d2f640a6
|
@ -206,7 +206,10 @@ export function useSlippageConfig() {
|
|||
return { slippage, setSlippage };
|
||||
}
|
||||
|
||||
const getErrorForTransaction = async (connection: Connection, txid: string) => {
|
||||
export const getErrorForTransaction = async (
|
||||
connection: Connection,
|
||||
txid: string,
|
||||
) => {
|
||||
// wait for all confirmation before geting transaction
|
||||
await connection.confirmTransaction(txid, 'max');
|
||||
|
||||
|
@ -239,6 +242,7 @@ export const sendTransaction = async (
|
|||
instructions: TransactionInstruction[],
|
||||
signers: Account[],
|
||||
awaitConfirmation = true,
|
||||
commitment = 'singleGossip',
|
||||
) => {
|
||||
let transaction = new Transaction();
|
||||
instructions.forEach(instruction => transaction.add(instruction));
|
||||
|
@ -257,7 +261,7 @@ export const sendTransaction = async (
|
|||
const rawTransaction = transaction.serialize();
|
||||
let options = {
|
||||
skipPreflight: true,
|
||||
commitment: 'singleGossip',
|
||||
commitment,
|
||||
};
|
||||
|
||||
const txid = await connection.sendRawTransaction(rawTransaction, options);
|
||||
|
|
|
@ -15,6 +15,9 @@ export let LENDING_PROGRAM_ID = new PublicKey(
|
|||
export let SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID = new PublicKey(
|
||||
'ATokenGPvbdGVxr1b2hvZbsiqW5xWH25efTNsLJA8knL',
|
||||
);
|
||||
export let BPF_UPGRADE_LOADER_ID = new PublicKey(
|
||||
'BPFLoaderUpgradeab1e11111111111111111111111',
|
||||
);
|
||||
|
||||
let WORMHOLE_BRIDGE: {
|
||||
pubkey: PublicKey;
|
||||
|
@ -90,9 +93,9 @@ export const PROGRAM_IDS = [
|
|||
name: 'devnet',
|
||||
timelock: () => ({
|
||||
programAccountId: new PublicKey(
|
||||
'rgq8xnCzKtGcaWCqbb9nAiJkk7vgjohTbJVgPRQoxQc',
|
||||
'FNsF5k1dGz8mrq7unFeeNx8LqFn9bhKg6n6N5DLgQmfb',
|
||||
),
|
||||
programId: new PublicKey('DwFgNNwigPgAiiexQXcXnKi4JU7UUtfk9vfcFrQ5sDTc'),
|
||||
programId: new PublicKey('6FyMHpXABKVSt4DmqUYLMgWChJV8HFXJuF6CsgucbZ3G'),
|
||||
}),
|
||||
wormhole: () => ({
|
||||
pubkey: new PublicKey('WormT3McKhFJ2RkiGpdw9GKvNCrB2aB54gb2uV9MfQC'),
|
||||
|
@ -163,5 +166,6 @@ export const programIds = () => {
|
|||
wormhole: WORMHOLE_BRIDGE,
|
||||
timelock: TIMELOCK,
|
||||
associatedToken: SPL_ASSOCIATED_TOKEN_ACCOUNT_PROGRAM_ID,
|
||||
bpf_upgrade_loader: BPF_UPGRADE_LOADER_ID,
|
||||
};
|
||||
};
|
||||
|
|
|
@ -247,3 +247,7 @@ export function convert(
|
|||
|
||||
return result;
|
||||
}
|
||||
|
||||
export function sleep(ms: number): Promise<void> {
|
||||
return new Promise(resolve => setTimeout(resolve, ms));
|
||||
}
|
||||
|
|
|
@ -1,24 +1,19 @@
|
|||
import {
|
||||
Account,
|
||||
CompiledInstruction,
|
||||
Connection,
|
||||
Message,
|
||||
PublicKey,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import { contexts, utils, models, ParsedAccount } from '@oyster/common';
|
||||
import bs58 from 'bs58';
|
||||
import {
|
||||
CustomSingleSignerTimelockTransactionLayout,
|
||||
INSTRUCTION_LIMIT,
|
||||
TimelockSet,
|
||||
} from '../models/timelock';
|
||||
import { addCustomSingleSignerTransactionInstruction } from '../models/addCustomSingleSignerTransaction';
|
||||
import { pingInstruction } from '../models/ping';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { signInstruction } from '../models/sign';
|
||||
import { serializeInstruction } from '../utils/serialize';
|
||||
|
||||
const { sendTransaction } = contexts.Connection;
|
||||
const { notify, shortvec, toUTF8Array, fromUTF8Array } = utils;
|
||||
|
@ -69,11 +64,25 @@ export const addCustomSingleSignerTransaction = async (
|
|||
1,
|
||||
);
|
||||
signers.push(transferAuthority);
|
||||
instruction = await serializeInstruction2({
|
||||
connection,
|
||||
wallet,
|
||||
instr: pingInstruction(),
|
||||
});
|
||||
|
||||
/*instruction = (
|
||||
await serializeInstruction({
|
||||
connection,
|
||||
instr: pingInstruction(),
|
||||
})
|
||||
).base64;
|
||||
|
||||
console.log(pingInstruction());
|
||||
const asArr = (
|
||||
await serializeInstruction({
|
||||
connection,
|
||||
instr: pingInstruction(),
|
||||
})
|
||||
).byteArray;
|
||||
|
||||
console.log(asArr);
|
||||
console.log('Message', Message.from(asArr));*/
|
||||
|
||||
instructions.push(
|
||||
addCustomSingleSignerTransactionInstruction(
|
||||
txnKey.publicKey,
|
||||
|
@ -113,135 +122,3 @@ export const addCustomSingleSignerTransaction = async (
|
|||
throw new Error();
|
||||
}
|
||||
};
|
||||
|
||||
async function serializeInstruction2({
|
||||
connection,
|
||||
wallet,
|
||||
instr,
|
||||
}: {
|
||||
connection: Connection;
|
||||
wallet: any;
|
||||
instr: TransactionInstruction;
|
||||
}): Promise<string> {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
let instructionTransaction = new Transaction();
|
||||
instructionTransaction.add(instr);
|
||||
instructionTransaction.recentBlockhash = (
|
||||
await connection.getRecentBlockhash('max')
|
||||
).blockhash;
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[PROGRAM_IDS.timelock.programAccountId.toBuffer()],
|
||||
PROGRAM_IDS.timelock.programId,
|
||||
);
|
||||
instructionTransaction.setSigners(authority);
|
||||
const msg: Message = instructionTransaction.compileMessage();
|
||||
|
||||
console.log('message', msg);
|
||||
console.log('from', Message.from(msg.serialize()));
|
||||
console.log(
|
||||
msg.serialize(),
|
||||
toUTF8Array(
|
||||
atob(fromUTF8Array(toUTF8Array(msg.serialize().toString('base64')))),
|
||||
),
|
||||
);
|
||||
let binary_string = atob(msg.serialize().toString('base64'));
|
||||
let len = binary_string.length;
|
||||
let bytes = new Uint8Array(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
console.log('from again', Message.from(bytes));
|
||||
return msg.serialize().toString('base64');
|
||||
}
|
||||
|
||||
async function serializeInstruction({
|
||||
connection,
|
||||
wallet,
|
||||
instr,
|
||||
}: {
|
||||
connection: Connection;
|
||||
wallet: any;
|
||||
instr: TransactionInstruction;
|
||||
}): Promise<string> {
|
||||
let instructionTransaction = new Transaction();
|
||||
instructionTransaction.add(instr);
|
||||
instructionTransaction.recentBlockhash = (
|
||||
await connection.getRecentBlockhash('max')
|
||||
).blockhash;
|
||||
// We dont actually signed, we just set this to get past a throw condition in compileMessage
|
||||
instructionTransaction.setSigners(
|
||||
// fee payied by the wallet owner
|
||||
wallet.publicKey,
|
||||
);
|
||||
const msg: Message = instructionTransaction.compileMessage();
|
||||
|
||||
const numKeys = msg.accountKeys.length;
|
||||
|
||||
let keyCount: number[] = [];
|
||||
shortvec.encodeLength(keyCount, numKeys);
|
||||
|
||||
const instruction = msg.instructions[0];
|
||||
const { accounts, programIdIndex } = instruction;
|
||||
const data = bs58.decode(instruction.data);
|
||||
|
||||
let keyIndicesCount: number[] = [];
|
||||
shortvec.encodeLength(keyIndicesCount, accounts.length);
|
||||
|
||||
let dataCount: number[] = [];
|
||||
shortvec.encodeLength(dataCount, data.length);
|
||||
|
||||
const instructionMeta = {
|
||||
programIdIndex,
|
||||
keyIndicesCount: Buffer.from(keyIndicesCount),
|
||||
keyIndices: Buffer.from(accounts),
|
||||
dataLength: Buffer.from(dataCount),
|
||||
data,
|
||||
};
|
||||
|
||||
let instructionBuffer = Buffer.alloc(100);
|
||||
|
||||
const instructionLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('programIdIndex'),
|
||||
|
||||
BufferLayout.blob(
|
||||
instructionMeta.keyIndicesCount.length,
|
||||
'keyIndicesCount',
|
||||
),
|
||||
BufferLayout.seq(
|
||||
BufferLayout.u8('keyIndex'),
|
||||
instructionMeta.keyIndices.length,
|
||||
'keyIndices',
|
||||
),
|
||||
BufferLayout.blob(instructionMeta.dataLength.length, 'dataLength'),
|
||||
BufferLayout.seq(
|
||||
BufferLayout.u8('userdatum'),
|
||||
instruction.data.length,
|
||||
'data',
|
||||
),
|
||||
]);
|
||||
instructionLayout.encode(instructionMeta, instructionBuffer);
|
||||
console.log(instruction);
|
||||
console.log(instructionBuffer);
|
||||
console.log(instructionBuffer.length);
|
||||
|
||||
console.log(instructionBuffer.toString('base64'));
|
||||
console.log(instructionBuffer.toString('base64').length);
|
||||
console.log(decodeBufferIntoInstruction(instructionBuffer));
|
||||
|
||||
return instructionBuffer.toString('base64');
|
||||
}
|
||||
|
||||
// For testing, eventually can be used agains tbase64 string (turn into bytes) to figure out accounts and
|
||||
// stuff, maybe display something to user. Decode.
|
||||
function decodeBufferIntoInstruction(instructionBuffer: Buffer) {
|
||||
let byteArray = [...instructionBuffer];
|
||||
let decodedInstruction: Partial<CompiledInstruction> = {};
|
||||
decodedInstruction.programIdIndex = byteArray.shift();
|
||||
const accountCount = shortvec.decodeLength(byteArray);
|
||||
decodedInstruction.accounts = byteArray.slice(0, accountCount);
|
||||
byteArray = byteArray.slice(accountCount);
|
||||
const dataLength = shortvec.decodeLength(byteArray);
|
||||
const data = byteArray.slice(0, dataLength);
|
||||
decodedInstruction.data = bs58.encode(Buffer.from(data));
|
||||
return decodedInstruction;
|
||||
}
|
||||
|
|
|
@ -30,18 +30,21 @@ export const execute = async (
|
|||
);
|
||||
|
||||
const actualMessage = decodeBufferIntoMessage(transaction.info.instruction);
|
||||
console.log(actualMessage);
|
||||
console.log('Actual message', actualMessage);
|
||||
const accountInfos = getAccountInfos(actualMessage);
|
||||
|
||||
instructions.push(
|
||||
executeInstruction(
|
||||
transaction.pubkey,
|
||||
proposal.pubkey,
|
||||
actualMessage.accountKeys[actualMessage.instructions[0].programIdIndex],
|
||||
authority,
|
||||
accountInfos,
|
||||
),
|
||||
);
|
||||
|
||||
notify({
|
||||
message: LABELS.ADDING_VOTES_TO_VOTER,
|
||||
message: LABELS.EXECUTING,
|
||||
description: LABELS.PLEASE_WAIT,
|
||||
type: 'warn',
|
||||
});
|
||||
|
@ -56,7 +59,7 @@ export const execute = async (
|
|||
);
|
||||
|
||||
notify({
|
||||
message: LABELS.VOTES_ADDED,
|
||||
message: LABELS.EXECUTED,
|
||||
type: 'success',
|
||||
description: LABELS.TRANSACTION + ` ${tx}`,
|
||||
});
|
||||
|
@ -66,15 +69,68 @@ export const execute = async (
|
|||
}
|
||||
};
|
||||
|
||||
function decodeBufferIntoMessage(instruction: string): Message {
|
||||
// stored as a base64, we need to convert back from base64(via atob), then convert that decoded
|
||||
// to a utf8 array, then decode that buffer into instruction
|
||||
|
||||
let binaryString = atob(instruction);
|
||||
let len = binaryString.length;
|
||||
let byteArray = new Uint8Array(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
byteArray[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
return Message.from(byteArray);
|
||||
function decodeBufferIntoMessage(instruction: number[]): Message {
|
||||
return Message.from(instruction);
|
||||
}
|
||||
|
||||
function getAccountInfos(
|
||||
actualMessage: Message,
|
||||
): { pubkey: PublicKey; isSigner: boolean; isWritable: boolean }[] {
|
||||
console.log(actualMessage);
|
||||
// From Solana docs:
|
||||
/*
|
||||
|
||||
The addresses that require signatures appear at the beginning of the account address array,
|
||||
with addresses requesting write access first and read-only accounts following.
|
||||
The addresses that do not require signatures follow the addresses that do,
|
||||
again with read-write accounts first and read-only accounts following.
|
||||
*/
|
||||
const accountInfosInOrder = actualMessage.instructions[0].accounts.map(
|
||||
a => actualMessage.accountKeys[a],
|
||||
);
|
||||
const requireSigsOnlyNotWritable =
|
||||
actualMessage.header.numReadonlySignedAccounts;
|
||||
const requireNietherSigsNorWrite =
|
||||
actualMessage.header.numReadonlyUnsignedAccounts;
|
||||
const writableOnly =
|
||||
accountInfosInOrder.length -
|
||||
requireSigsOnlyNotWritable -
|
||||
requireNietherSigsNorWrite;
|
||||
const readOnly = requireSigsOnlyNotWritable + requireNietherSigsNorWrite;
|
||||
|
||||
let position = 0;
|
||||
|
||||
let finalArray: {
|
||||
pubkey: PublicKey;
|
||||
isSigner: boolean;
|
||||
isWritable: boolean;
|
||||
}[] = [];
|
||||
for (let i = 0; i < writableOnly; i++) {
|
||||
finalArray.push({
|
||||
pubkey: accountInfosInOrder[position],
|
||||
isWritable: true,
|
||||
isSigner: false, // We force signer to false because you realistically as executor wont
|
||||
// have any of these keys present unless it happens to be your own
|
||||
// WE dont care about required signers or not
|
||||
});
|
||||
position++;
|
||||
}
|
||||
|
||||
for (let i = 0; i < readOnly; i++) {
|
||||
finalArray.push({
|
||||
pubkey: accountInfosInOrder[position],
|
||||
isWritable: false,
|
||||
isSigner: false,
|
||||
});
|
||||
position++;
|
||||
}
|
||||
|
||||
for (; position < accountInfosInOrder.length; position++) {
|
||||
finalArray.push({
|
||||
pubkey: accountInfosInOrder[position],
|
||||
isWritable: false,
|
||||
isSigner: false,
|
||||
});
|
||||
}
|
||||
return finalArray;
|
||||
}
|
||||
|
|
|
@ -4,13 +4,7 @@ import {
|
|||
PublicKey,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import {
|
||||
contexts,
|
||||
utils,
|
||||
models,
|
||||
ParsedAccount,
|
||||
actions,
|
||||
} from '@oyster/common';
|
||||
import { contexts, utils, models, ParsedAccount } from '@oyster/common';
|
||||
|
||||
import { TimelockSet } from '../models/timelock';
|
||||
import { removeSignerInstruction } from '../models/removeSigner';
|
||||
|
|
|
@ -1,10 +1,13 @@
|
|||
import React from 'react';
|
||||
import { Card, Spin } from 'antd';
|
||||
import React, { useState } from 'react';
|
||||
import { Card, Progress, Spin } from 'antd';
|
||||
import { Form, Input } from 'antd';
|
||||
import { INSTRUCTION_LIMIT, TimelockSet } from '../../models/timelock';
|
||||
import { contexts, ParsedAccount, hooks, utils } from '@oyster/common';
|
||||
import { addCustomSingleSignerTransaction } from '../../actions/addCustomSingleSignerTransaction';
|
||||
import { SaveOutlined } from '@ant-design/icons';
|
||||
import { Connection, PublicKey } from '@solana/web3.js';
|
||||
import { initializeBuffer } from '../../actions/initializeBuffer';
|
||||
import { loadBufferAccount } from '../../actions/loadBufferAccount';
|
||||
|
||||
const { useWallet } = contexts.Wallet;
|
||||
const { useConnection } = contexts.Connection;
|
||||
|
@ -16,6 +19,11 @@ const layout = {
|
|||
wrapperCol: { span: 16 },
|
||||
};
|
||||
|
||||
enum UploadType {
|
||||
Base64 = 'Base64',
|
||||
Upgrade = 'Upgrade',
|
||||
}
|
||||
|
||||
export function NewInstructionCard({
|
||||
proposal,
|
||||
position,
|
||||
|
@ -27,7 +35,16 @@ export function NewInstructionCard({
|
|||
const wallet = useWallet();
|
||||
const connection = useConnection();
|
||||
const sigAccount = useAccountByMint(proposal.info.signatoryMint);
|
||||
const onFinish = async (values: { slot: string; instruction: string }) => {
|
||||
const [tabKey, setTabKey] = useState<UploadType>(UploadType.Base64);
|
||||
const [inputRef, setInputRef] = useState<Input | null>(null);
|
||||
const [savePerc, setSavePerc] = useState(0);
|
||||
const [saving, setSaving] = useState(false);
|
||||
|
||||
const onFinish = async (values: {
|
||||
slot: string;
|
||||
instruction: string;
|
||||
destination?: string;
|
||||
}) => {
|
||||
if (!values.slot.match(/^\d*$/)) {
|
||||
notify({
|
||||
message: 'Slot can only be numeric',
|
||||
|
@ -35,6 +52,24 @@ export function NewInstructionCard({
|
|||
});
|
||||
return;
|
||||
}
|
||||
|
||||
let instruction = values.instruction;
|
||||
|
||||
if (inputRef?.input?.files) {
|
||||
// Crap, we need to fully upload first...
|
||||
await handleUploadBpf({
|
||||
inputRef,
|
||||
connection,
|
||||
wallet,
|
||||
proposal,
|
||||
sigAccountKey: sigAccount?.pubkey,
|
||||
setSavePerc,
|
||||
setSaving,
|
||||
});
|
||||
// return for now...
|
||||
return;
|
||||
}
|
||||
|
||||
if (sigAccount) {
|
||||
await addCustomSingleSignerTransaction(
|
||||
connection,
|
||||
|
@ -42,17 +77,15 @@ export function NewInstructionCard({
|
|||
proposal,
|
||||
sigAccount.pubkey,
|
||||
values.slot,
|
||||
values.instruction,
|
||||
instruction,
|
||||
position,
|
||||
);
|
||||
form.resetFields();
|
||||
}
|
||||
};
|
||||
return !sigAccount ? null : (
|
||||
<Card
|
||||
title="New Instruction"
|
||||
actions={[<SaveOutlined key="save" onClick={form.submit} />]}
|
||||
>
|
||||
|
||||
const content = {
|
||||
[UploadType.Base64]: (
|
||||
<Form {...layout} form={form} name="control-hooks" onFinish={onFinish}>
|
||||
<Form.Item name="slot" label="Slot" rules={[{ required: true }]}>
|
||||
<Input maxLength={64} />
|
||||
|
@ -64,10 +97,102 @@ export function NewInstructionCard({
|
|||
>
|
||||
<Input
|
||||
maxLength={INSTRUCTION_LIMIT}
|
||||
placeholder={'Base58 encoded instruction'}
|
||||
placeholder={
|
||||
"Base64 encoded Solana Message object with single instruction (call message.serialize().toString('base64')) no more than 255 characters"
|
||||
}
|
||||
/>
|
||||
</Form.Item>
|
||||
</Form>
|
||||
),
|
||||
[UploadType.Upgrade]: (
|
||||
<Form {...layout} form={form} name="control-hooks" onFinish={onFinish}>
|
||||
<Form.Item name="slot" label="Slot" rules={[{ required: true }]}>
|
||||
<Input maxLength={64} />
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="destination"
|
||||
label="Program Address"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input
|
||||
maxLength={INSTRUCTION_LIMIT}
|
||||
placeholder={'Program Address to Update (Base 58)'}
|
||||
/>
|
||||
</Form.Item>
|
||||
<Form.Item
|
||||
name="instruction"
|
||||
label="Instruction"
|
||||
rules={[{ required: true }]}
|
||||
>
|
||||
<Input type="file" ref={ref => setInputRef(ref)} />
|
||||
</Form.Item>
|
||||
</Form>
|
||||
),
|
||||
};
|
||||
return !sigAccount ? null : (
|
||||
<Card
|
||||
title="New Instruction"
|
||||
tabList={[
|
||||
{ key: UploadType.Base64, tab: 'Custom Instruction' },
|
||||
/*{ key: UploadType.Upgrade, tab: 'Program Upgrade' },*/
|
||||
]}
|
||||
activeTabKey={tabKey}
|
||||
onTabChange={key =>
|
||||
setTabKey(key === 'Base64' ? UploadType.Base64 : UploadType.Upgrade)
|
||||
}
|
||||
actions={[<SaveOutlined key="save" onClick={form.submit} />]}
|
||||
>
|
||||
{saving && <Progress percent={savePerc} status="active" />}
|
||||
{content[tabKey]}
|
||||
</Card>
|
||||
);
|
||||
}
|
||||
|
||||
async function handleUploadBpf({
|
||||
inputRef,
|
||||
connection,
|
||||
wallet,
|
||||
proposal,
|
||||
sigAccountKey,
|
||||
setSavePerc,
|
||||
setSaving,
|
||||
}: {
|
||||
inputRef: Input;
|
||||
connection: Connection;
|
||||
wallet: any;
|
||||
proposal: ParsedAccount<TimelockSet>;
|
||||
sigAccountKey: PublicKey | undefined;
|
||||
setSavePerc: React.Dispatch<React.SetStateAction<number>>;
|
||||
setSaving: React.Dispatch<React.SetStateAction<boolean>>;
|
||||
}) {
|
||||
if (sigAccountKey)
|
||||
return new Promise(res => {
|
||||
const reader = new FileReader();
|
||||
reader.onload = async function () {
|
||||
const bytes = new Uint8Array(reader.result as ArrayBuffer);
|
||||
const len = bytes.byteLength;
|
||||
|
||||
setSaving(true);
|
||||
try {
|
||||
const tempFile = await initializeBuffer(
|
||||
connection,
|
||||
wallet.wallet,
|
||||
len,
|
||||
);
|
||||
await loadBufferAccount(
|
||||
connection,
|
||||
wallet.wallet,
|
||||
tempFile,
|
||||
bytes,
|
||||
setSavePerc,
|
||||
);
|
||||
} catch (e) {
|
||||
console.error(e);
|
||||
}
|
||||
setSaving(false);
|
||||
setSavePerc(0);
|
||||
res(true);
|
||||
};
|
||||
reader.readAsArrayBuffer((inputRef?.input?.files || [])[0]);
|
||||
});
|
||||
}
|
||||
|
|
|
@ -55,4 +55,6 @@ export const LABELS = {
|
|||
BURNING_VOTES: 'Burning your votes...',
|
||||
VOTES_BURNED: 'Votes burned',
|
||||
VOTE: 'Vote',
|
||||
EXECUTING: 'Executing...',
|
||||
EXECUTED: 'Executed.',
|
||||
};
|
||||
|
|
|
@ -32,13 +32,19 @@ export const addCustomSingleSignerTransactionInstruction = (
|
|||
transferAuthority: PublicKey,
|
||||
authority: PublicKey,
|
||||
slot: string,
|
||||
instruction: string,
|
||||
instruction: string, // base64 encoded
|
||||
position: number,
|
||||
): TransactionInstruction => {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
// need to get a pda, move blockhash out of here...
|
||||
|
||||
const instructionAsBytes = toUTF8Array(instruction);
|
||||
let binaryString = atob(instruction);
|
||||
let len = binaryString.length;
|
||||
let bytes = new Uint8Array(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
bytes[i] = binaryString.charCodeAt(i);
|
||||
}
|
||||
const instructionAsBytes = [...bytes];
|
||||
if (instructionAsBytes.length > INSTRUCTION_LIMIT) {
|
||||
throw new Error(
|
||||
'Instruction length in bytes is more than ' + INSTRUCTION_LIMIT,
|
||||
|
@ -56,7 +62,7 @@ export const addCustomSingleSignerTransactionInstruction = (
|
|||
Layout.uint64('slot'),
|
||||
BufferLayout.seq(BufferLayout.u8(), INSTRUCTION_LIMIT, 'instructions'),
|
||||
BufferLayout.u8('position'),
|
||||
BufferLayout.u8('instructionEndIndex'),
|
||||
BufferLayout.u16('instructionEndIndex'),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
@ -64,7 +70,6 @@ export const addCustomSingleSignerTransactionInstruction = (
|
|||
for (let i = instructionAsBytes.length; i <= INSTRUCTION_LIMIT - 1; i++) {
|
||||
instructionAsBytes.push(0);
|
||||
}
|
||||
console.log('Instruction end index', instructionEndIndex);
|
||||
|
||||
dataLayout.encode(
|
||||
{
|
||||
|
|
|
@ -1,4 +1,8 @@
|
|||
import { PublicKey, TransactionInstruction } from '@solana/web3.js';
|
||||
import {
|
||||
PublicKey,
|
||||
SYSVAR_CLOCK_PUBKEY,
|
||||
TransactionInstruction,
|
||||
} from '@solana/web3.js';
|
||||
import { utils } from '@oyster/common';
|
||||
import * as BufferLayout from 'buffer-layout';
|
||||
import { TimelockInstruction } from './timelock';
|
||||
|
@ -10,24 +14,35 @@ import { TimelockInstruction } from './timelock';
|
|||
/// 2. `[]` Program being invoked account
|
||||
/// 3. `[]` Timelock program authority
|
||||
/// 4. `[]` Timelock program account pub key.
|
||||
/// 5. `[]` Clock sysvar.
|
||||
/// 6+ Any extra accounts that are part of the instruction, in order
|
||||
export const executeInstruction = (
|
||||
transactionAccount: PublicKey,
|
||||
timelockSetAccount: PublicKey,
|
||||
programBeingInvokedAccount: PublicKey,
|
||||
timelockAuthority: PublicKey,
|
||||
accountInfos: { pubkey: PublicKey; isWritable: boolean; isSigner: boolean }[],
|
||||
): TransactionInstruction => {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
|
||||
const dataLayout = BufferLayout.struct([BufferLayout.u8('instruction')]);
|
||||
const dataLayout = BufferLayout.struct([
|
||||
BufferLayout.u8('instruction'),
|
||||
BufferLayout.u8('numberOfExtraAccounts'),
|
||||
]);
|
||||
|
||||
const data = Buffer.alloc(dataLayout.span);
|
||||
|
||||
dataLayout.encode(
|
||||
{
|
||||
instruction: TimelockInstruction.Execute,
|
||||
numberOfExtraAccounts: accountInfos.length,
|
||||
},
|
||||
data,
|
||||
);
|
||||
console.log(
|
||||
'Acohjnt',
|
||||
accountInfos.map(a => console.log(a.pubkey.toBase58(), a.isWritable)),
|
||||
);
|
||||
|
||||
const keys = [
|
||||
{ pubkey: transactionAccount, isSigner: false, isWritable: true },
|
||||
|
@ -39,6 +54,8 @@ export const executeInstruction = (
|
|||
isSigner: false,
|
||||
isWritable: false,
|
||||
},
|
||||
{ pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false },
|
||||
...accountInfos,
|
||||
];
|
||||
return new TransactionInstruction({
|
||||
keys,
|
||||
|
|
|
@ -6,8 +6,9 @@ import { utils } from '@oyster/common';
|
|||
|
||||
export const DESC_SIZE = 200;
|
||||
export const NAME_SIZE = 32;
|
||||
export const INSTRUCTION_LIMIT = 255;
|
||||
export const INSTRUCTION_LIMIT = 500;
|
||||
export const TRANSACTION_SLOTS = 10;
|
||||
export const TEMP_FILE_TXN_SIZE = 1000;
|
||||
|
||||
export enum TimelockInstruction {
|
||||
InitTimelockSet = 1,
|
||||
|
@ -19,6 +20,7 @@ export enum TimelockInstruction {
|
|||
MintVotingTokens = 10,
|
||||
Ping = 11,
|
||||
Execute = 12,
|
||||
UploadTempFile = 13,
|
||||
}
|
||||
|
||||
export interface TimelockConfig {
|
||||
|
@ -191,10 +193,8 @@ export const CustomSingleSignerTimelockTransactionParser = (
|
|||
info: {
|
||||
version: data.version,
|
||||
slot: data.slot,
|
||||
instruction: utils.fromUTF8Array(
|
||||
data.instruction.slice(0, data.instructionEndIndex),
|
||||
),
|
||||
authorityKey: data.authorityKey,
|
||||
instruction: data.instruction.slice(0, data.instructionEndIndex + 1),
|
||||
|
||||
executed: data.executed,
|
||||
instructionEndIndex: data.instructionEndIndex,
|
||||
},
|
||||
|
@ -208,9 +208,8 @@ export const CustomSingleSignerTimelockTransactionLayout: typeof BufferLayout.St
|
|||
BufferLayout.u8('version'),
|
||||
Layout.uint64('slot'),
|
||||
BufferLayout.seq(BufferLayout.u8(), INSTRUCTION_LIMIT, 'instruction'),
|
||||
Layout.publicKey('authorityKey'),
|
||||
BufferLayout.u8('executed'),
|
||||
BufferLayout.u8('instructionEndIndex'),
|
||||
BufferLayout.u16('instructionEndIndex'),
|
||||
],
|
||||
);
|
||||
|
||||
|
@ -219,13 +218,11 @@ export interface TimelockTransaction {
|
|||
|
||||
slot: BN;
|
||||
|
||||
instruction: string;
|
||||
instruction: number[];
|
||||
|
||||
executed: number;
|
||||
|
||||
instructionEndIndex: number;
|
||||
}
|
||||
export interface CustomSingleSignerTimelockTransaction
|
||||
extends TimelockTransaction {
|
||||
authorityKey: PublicKey;
|
||||
}
|
||||
extends TimelockTransaction {}
|
||||
|
|
|
@ -0,0 +1,39 @@
|
|||
import { utils } from '@oyster/common';
|
||||
import {
|
||||
Connection,
|
||||
TransactionInstruction,
|
||||
Transaction,
|
||||
PublicKey,
|
||||
Message,
|
||||
} from '@solana/web3.js';
|
||||
export async function serializeInstruction({
|
||||
connection,
|
||||
instr,
|
||||
}: {
|
||||
connection: Connection;
|
||||
instr: TransactionInstruction;
|
||||
}): Promise<{ base64: string; byteArray: Uint8Array }> {
|
||||
const PROGRAM_IDS = utils.programIds();
|
||||
let instructionTransaction = new Transaction();
|
||||
instructionTransaction.add(instr);
|
||||
instructionTransaction.recentBlockhash = (
|
||||
await connection.getRecentBlockhash('max')
|
||||
).blockhash;
|
||||
const [authority] = await PublicKey.findProgramAddress(
|
||||
[PROGRAM_IDS.timelock.programAccountId.toBuffer()],
|
||||
PROGRAM_IDS.timelock.programId,
|
||||
);
|
||||
instructionTransaction.setSigners(authority);
|
||||
const msg: Message = instructionTransaction.compileMessage();
|
||||
|
||||
let binary_string = atob(msg.serialize().toString('base64'));
|
||||
let len = binary_string.length;
|
||||
let bytes = new Uint8Array(len);
|
||||
for (var i = 0; i < len; i++) {
|
||||
bytes[i] = binary_string.charCodeAt(i);
|
||||
}
|
||||
return {
|
||||
base64: msg.serialize().toString('base64'),
|
||||
byteArray: bytes,
|
||||
};
|
||||
}
|
Loading…
Reference in New Issue