fix: add integration test and fix various exposed bugs
This commit is contained in:
committed by
Michael Vines
parent
07c0670f65
commit
3595892fab
@ -203,9 +203,6 @@ declare module '@solana/web3.js' {
|
|||||||
validatorExit(): Promise<boolean>;
|
validatorExit(): Promise<boolean>;
|
||||||
}
|
}
|
||||||
|
|
||||||
// === src/config-program.js ===
|
|
||||||
declare export var CONFIG_PROGRAM_ID;
|
|
||||||
|
|
||||||
// === src/stake-program.js ===
|
// === src/stake-program.js ===
|
||||||
declare export class StakeProgram {
|
declare export class StakeProgram {
|
||||||
static programId: PublicKey;
|
static programId: PublicKey;
|
||||||
|
@ -1,6 +0,0 @@
|
|||||||
// @flow
|
|
||||||
import {PublicKey} from './publickey';
|
|
||||||
|
|
||||||
export const CONFIG_PROGRAM_ID = new PublicKey(
|
|
||||||
'Config1111111111111111111111111111111111111',
|
|
||||||
);
|
|
@ -2,11 +2,11 @@
|
|||||||
export {Account} from './account';
|
export {Account} from './account';
|
||||||
export {BpfLoader} from './bpf-loader';
|
export {BpfLoader} from './bpf-loader';
|
||||||
export {BudgetProgram} from './budget-program';
|
export {BudgetProgram} from './budget-program';
|
||||||
export {CONFIG_PROGRAM_ID} from './config-program';
|
|
||||||
export {Connection} from './connection';
|
export {Connection} from './connection';
|
||||||
export {Loader} from './loader';
|
export {Loader} from './loader';
|
||||||
export {PublicKey} from './publickey';
|
export {PublicKey} from './publickey';
|
||||||
export {
|
export {
|
||||||
|
STAKE_CONFIG_ID,
|
||||||
Authorized,
|
Authorized,
|
||||||
Lockup,
|
Lockup,
|
||||||
StakeAuthorizationLayout,
|
StakeAuthorizationLayout,
|
||||||
|
@ -69,7 +69,11 @@ export const authorized = (property: string = 'authorized') => {
|
|||||||
*/
|
*/
|
||||||
export const lockup = (property: string = 'lockup') => {
|
export const lockup = (property: string = 'lockup') => {
|
||||||
return BufferLayout.struct(
|
return BufferLayout.struct(
|
||||||
[BufferLayout.ns64('epoch'), publicKey('custodian')],
|
[
|
||||||
|
BufferLayout.ns64('unixTimestamp'),
|
||||||
|
BufferLayout.ns64('epoch'),
|
||||||
|
publicKey('custodian'),
|
||||||
|
],
|
||||||
property,
|
property,
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
@ -3,7 +3,6 @@
|
|||||||
import * as BufferLayout from 'buffer-layout';
|
import * as BufferLayout from 'buffer-layout';
|
||||||
import hasha from 'hasha';
|
import hasha from 'hasha';
|
||||||
|
|
||||||
import {CONFIG_PROGRAM_ID} from './config-program';
|
|
||||||
import {encodeData} from './instruction';
|
import {encodeData} from './instruction';
|
||||||
import type {InstructionType} from './instruction';
|
import type {InstructionType} from './instruction';
|
||||||
import * as Layout from './layout';
|
import * as Layout from './layout';
|
||||||
@ -18,6 +17,10 @@ import {
|
|||||||
import {Transaction, TransactionInstruction} from './transaction';
|
import {Transaction, TransactionInstruction} from './transaction';
|
||||||
import type {TransactionInstructionCtorFields} from './transaction';
|
import type {TransactionInstructionCtorFields} from './transaction';
|
||||||
|
|
||||||
|
export const STAKE_CONFIG_ID = new PublicKey(
|
||||||
|
'StakeConfig11111111111111111111111111111111',
|
||||||
|
);
|
||||||
|
|
||||||
export class Authorized {
|
export class Authorized {
|
||||||
staker: PublicKey;
|
staker: PublicKey;
|
||||||
withdrawer: PublicKey;
|
withdrawer: PublicKey;
|
||||||
@ -32,13 +35,15 @@ export class Authorized {
|
|||||||
}
|
}
|
||||||
|
|
||||||
export class Lockup {
|
export class Lockup {
|
||||||
|
unixTimestamp: number;
|
||||||
epoch: number;
|
epoch: number;
|
||||||
custodian: PublicKey;
|
custodian: PublicKey;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Create a new Authorized object
|
* Create a new Lockup object
|
||||||
*/
|
*/
|
||||||
constructor(epoch: number, custodian: PublicKey) {
|
constructor(unixTimestamp: number, epoch: number, custodian: PublicKey) {
|
||||||
|
this.unixTimestamp = unixTimestamp;
|
||||||
this.epoch = epoch;
|
this.epoch = epoch;
|
||||||
this.custodian = custodian;
|
this.custodian = custodian;
|
||||||
}
|
}
|
||||||
@ -126,14 +131,14 @@ export const StakeInstructionLayout = Object.freeze({
|
|||||||
index: 4,
|
index: 4,
|
||||||
layout: BufferLayout.struct([
|
layout: BufferLayout.struct([
|
||||||
BufferLayout.u32('instruction'),
|
BufferLayout.u32('instruction'),
|
||||||
BufferLayout.ns64('amount'),
|
BufferLayout.ns64('lamports'),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
Withdraw: {
|
Withdraw: {
|
||||||
index: 5,
|
index: 5,
|
||||||
layout: BufferLayout.struct([
|
layout: BufferLayout.struct([
|
||||||
BufferLayout.u32('instruction'),
|
BufferLayout.u32('instruction'),
|
||||||
BufferLayout.ns64('amount'),
|
BufferLayout.ns64('lamports'),
|
||||||
]),
|
]),
|
||||||
},
|
},
|
||||||
Deactivate: {
|
Deactivate: {
|
||||||
@ -197,7 +202,7 @@ export class StakeProgram {
|
|||||||
* Max space of a Stake account
|
* Max space of a Stake account
|
||||||
*/
|
*/
|
||||||
static get space(): number {
|
static get space(): number {
|
||||||
return 2000;
|
return 2008;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -215,6 +220,7 @@ export class StakeProgram {
|
|||||||
withdrawer: authorized.withdrawer.toBuffer(),
|
withdrawer: authorized.withdrawer.toBuffer(),
|
||||||
},
|
},
|
||||||
lockup: {
|
lockup: {
|
||||||
|
unixTimestamp: lockup.unixTimestamp,
|
||||||
epoch: lockup.epoch,
|
epoch: lockup.epoch,
|
||||||
custodian: lockup.custodian.toBuffer(),
|
custodian: lockup.custodian.toBuffer(),
|
||||||
},
|
},
|
||||||
@ -293,7 +299,7 @@ export class StakeProgram {
|
|||||||
{pubkey: stakeAccount, isSigner: false, isWritable: true},
|
{pubkey: stakeAccount, isSigner: false, isWritable: true},
|
||||||
{pubkey: votePubkey, isSigner: false, isWritable: false},
|
{pubkey: votePubkey, isSigner: false, isWritable: false},
|
||||||
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
|
{pubkey: SYSVAR_CLOCK_PUBKEY, isSigner: false, isWritable: false},
|
||||||
{pubkey: CONFIG_PROGRAM_ID, isSigner: false, isWritable: false},
|
{pubkey: STAKE_CONFIG_ID, isSigner: false, isWritable: false},
|
||||||
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
|
{pubkey: authorizedPubkey, isSigner: true, isWritable: false},
|
||||||
],
|
],
|
||||||
programId: this.programId,
|
programId: this.programId,
|
||||||
|
@ -3,8 +3,11 @@
|
|||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
Authorized,
|
Authorized,
|
||||||
|
Connection,
|
||||||
Lockup,
|
Lockup,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
sendAndConfirmRecentTransaction,
|
||||||
|
LAMPORTS_PER_SOL,
|
||||||
StakeAuthorizationLayout,
|
StakeAuthorizationLayout,
|
||||||
StakeInstruction,
|
StakeInstruction,
|
||||||
StakeInstructionLayout,
|
StakeInstructionLayout,
|
||||||
@ -13,6 +16,13 @@ import {
|
|||||||
SystemProgram,
|
SystemProgram,
|
||||||
Transaction,
|
Transaction,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
|
import {mockRpcEnabled} from './__mocks__/node-fetch';
|
||||||
|
import {url} from './url';
|
||||||
|
|
||||||
|
if (!mockRpcEnabled) {
|
||||||
|
// Testing max commitment level takes around 20s to complete
|
||||||
|
jest.setTimeout(30000);
|
||||||
|
}
|
||||||
|
|
||||||
test('createAccountWithSeed', () => {
|
test('createAccountWithSeed', () => {
|
||||||
const from = new Account();
|
const from = new Account();
|
||||||
@ -30,7 +40,7 @@ test('createAccountWithSeed', () => {
|
|||||||
newAccountPubkey,
|
newAccountPubkey,
|
||||||
seed,
|
seed,
|
||||||
new Authorized(authorized.publicKey, authorized.publicKey),
|
new Authorized(authorized.publicKey, authorized.publicKey),
|
||||||
new Lockup(0, from.publicKey),
|
new Lockup(0, 0, from.publicKey),
|
||||||
123,
|
123,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -52,7 +62,7 @@ test('createAccount', () => {
|
|||||||
from.publicKey,
|
from.publicKey,
|
||||||
newAccount.publicKey,
|
newAccount.publicKey,
|
||||||
new Authorized(authorized.publicKey, authorized.publicKey),
|
new Authorized(authorized.publicKey, authorized.publicKey),
|
||||||
new Lockup(0, from.publicKey),
|
new Lockup(0, 0, from.publicKey),
|
||||||
123,
|
123,
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -179,7 +189,7 @@ test('StakeInstructions', () => {
|
|||||||
newAccountPubkey,
|
newAccountPubkey,
|
||||||
seed,
|
seed,
|
||||||
new Authorized(authorized.publicKey, authorized.publicKey),
|
new Authorized(authorized.publicKey, authorized.publicKey),
|
||||||
new Lockup(0, from.publicKey),
|
new Lockup(0, 0, from.publicKey),
|
||||||
amount,
|
amount,
|
||||||
);
|
);
|
||||||
const createWithSeedTransaction = new Transaction({recentBlockhash}).add(
|
const createWithSeedTransaction = new Transaction({recentBlockhash}).add(
|
||||||
@ -220,3 +230,121 @@ test('StakeInstructions', () => {
|
|||||||
StakeInstructionLayout.DelegateStake,
|
StakeInstructionLayout.DelegateStake,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('live staking actions', async () => {
|
||||||
|
if (mockRpcEnabled) {
|
||||||
|
console.log('non-live test skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = new Connection(url, 'recent');
|
||||||
|
const voteAccounts = await connection.getVoteAccounts();
|
||||||
|
const voteAccount = voteAccounts.current.concat(voteAccounts.delinquent)[0];
|
||||||
|
const votePubkey = new PublicKey(voteAccount.votePubkey);
|
||||||
|
|
||||||
|
const from = new Account();
|
||||||
|
const authorized = new Account();
|
||||||
|
await connection.requestAirdrop(from.publicKey, LAMPORTS_PER_SOL);
|
||||||
|
await connection.requestAirdrop(authorized.publicKey, LAMPORTS_PER_SOL);
|
||||||
|
|
||||||
|
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
StakeProgram.space,
|
||||||
|
'recent',
|
||||||
|
);
|
||||||
|
|
||||||
|
// Create Stake account with seed
|
||||||
|
const seed = 'test string';
|
||||||
|
const newAccountPubkey = PublicKey.createWithSeed(
|
||||||
|
from.publicKey,
|
||||||
|
seed,
|
||||||
|
StakeProgram.programId,
|
||||||
|
);
|
||||||
|
|
||||||
|
let createAndInitializeWithSeed = StakeProgram.createAccountWithSeed(
|
||||||
|
from.publicKey,
|
||||||
|
newAccountPubkey,
|
||||||
|
seed,
|
||||||
|
new Authorized(authorized.publicKey, authorized.publicKey),
|
||||||
|
new Lockup(0, 0, new PublicKey('0x00')),
|
||||||
|
2 * minimumAmount + 42,
|
||||||
|
);
|
||||||
|
|
||||||
|
await sendAndConfirmRecentTransaction(
|
||||||
|
connection,
|
||||||
|
createAndInitializeWithSeed,
|
||||||
|
from,
|
||||||
|
);
|
||||||
|
let originalStakeBalance = await connection.getBalance(newAccountPubkey);
|
||||||
|
expect(originalStakeBalance).toEqual(2 * minimumAmount + 42);
|
||||||
|
|
||||||
|
let delegation = StakeProgram.delegate(
|
||||||
|
newAccountPubkey,
|
||||||
|
authorized.publicKey,
|
||||||
|
votePubkey,
|
||||||
|
);
|
||||||
|
await sendAndConfirmRecentTransaction(connection, delegation, authorized);
|
||||||
|
|
||||||
|
// Test that withdraw fails before deactivation
|
||||||
|
const recipient = new Account();
|
||||||
|
let withdraw = StakeProgram.withdraw(
|
||||||
|
newAccountPubkey,
|
||||||
|
authorized.publicKey,
|
||||||
|
recipient.publicKey,
|
||||||
|
1000,
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
sendAndConfirmRecentTransaction(connection, withdraw, authorized),
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
|
// Authorize to new account
|
||||||
|
const newAuthorized = new Account();
|
||||||
|
await connection.requestAirdrop(newAuthorized.publicKey, LAMPORTS_PER_SOL);
|
||||||
|
|
||||||
|
let authorize = StakeProgram.authorize(
|
||||||
|
newAccountPubkey,
|
||||||
|
authorized.publicKey,
|
||||||
|
newAuthorized.publicKey,
|
||||||
|
StakeAuthorizationLayout.Withdrawer,
|
||||||
|
);
|
||||||
|
await sendAndConfirmRecentTransaction(connection, authorize, authorized);
|
||||||
|
authorize = StakeProgram.authorize(
|
||||||
|
newAccountPubkey,
|
||||||
|
authorized.publicKey,
|
||||||
|
newAuthorized.publicKey,
|
||||||
|
StakeAuthorizationLayout.Staker,
|
||||||
|
);
|
||||||
|
await sendAndConfirmRecentTransaction(connection, authorize, authorized);
|
||||||
|
|
||||||
|
// Test old authorized can't deactivate
|
||||||
|
let deactivateNotAuthorized = StakeProgram.deactivate(
|
||||||
|
newAccountPubkey,
|
||||||
|
authorized.publicKey,
|
||||||
|
);
|
||||||
|
await expect(
|
||||||
|
sendAndConfirmRecentTransaction(
|
||||||
|
connection,
|
||||||
|
deactivateNotAuthorized,
|
||||||
|
authorized,
|
||||||
|
),
|
||||||
|
).rejects.toThrow();
|
||||||
|
|
||||||
|
// Deactivate stake
|
||||||
|
let deactivate = StakeProgram.deactivate(
|
||||||
|
newAccountPubkey,
|
||||||
|
newAuthorized.publicKey,
|
||||||
|
);
|
||||||
|
await sendAndConfirmRecentTransaction(connection, deactivate, newAuthorized);
|
||||||
|
|
||||||
|
// Test that withdraw succeeds after deactivation
|
||||||
|
withdraw = StakeProgram.withdraw(
|
||||||
|
newAccountPubkey,
|
||||||
|
newAuthorized.publicKey,
|
||||||
|
recipient.publicKey,
|
||||||
|
minimumAmount + 20,
|
||||||
|
);
|
||||||
|
await sendAndConfirmRecentTransaction(connection, withdraw, newAuthorized);
|
||||||
|
const balance = await connection.getBalance(newAccountPubkey);
|
||||||
|
expect(balance).toEqual(minimumAmount + 22);
|
||||||
|
const recipientBalance = await connection.getBalance(recipient.publicKey);
|
||||||
|
expect(recipientBalance).toEqual(minimumAmount + 20);
|
||||||
|
});
|
||||||
|
Reference in New Issue
Block a user