feat: add getStakeActivation (#12274)
* feat: add getStakeActivation * chore: add rollup watch * feat: use string literal for stake activation state * fix: remove optional chaining due to issue with esdoc * chore: remove optional_chaining * feat: add live test for getStakeActivation * feat: extend _buildArgs to support additional options, simplify unit test
This commit is contained in:
parent
8d6af087a2
commit
63db4759f8
|
@ -197,6 +197,12 @@ declare module '@solana/web3.js' {
|
||||||
space: number;
|
space: number;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
export type StakeActivationData = {
|
||||||
|
state: 'active' | 'inactive' | 'activating' | 'deactivating';
|
||||||
|
active: number;
|
||||||
|
inactive: number;
|
||||||
|
};
|
||||||
|
|
||||||
export type KeyedAccountInfo = {
|
export type KeyedAccountInfo = {
|
||||||
accountId: PublicKey;
|
accountId: PublicKey;
|
||||||
accountInfo: AccountInfo<Buffer>;
|
accountInfo: AccountInfo<Buffer>;
|
||||||
|
@ -314,6 +320,11 @@ declare module '@solana/web3.js' {
|
||||||
): Promise<
|
): Promise<
|
||||||
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>
|
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>
|
||||||
>;
|
>;
|
||||||
|
getStakeActivation(
|
||||||
|
publicKey: PublicKey,
|
||||||
|
commitment?: Commitment,
|
||||||
|
epoch?: number,
|
||||||
|
): Promise<StakeActivationData>;
|
||||||
getProgramAccounts(
|
getProgramAccounts(
|
||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
commitment?: Commitment,
|
commitment?: Commitment,
|
||||||
|
|
|
@ -178,6 +178,12 @@ declare module '@solana/web3.js' {
|
||||||
space: number,
|
space: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
declare export type StakeActivationData = {
|
||||||
|
state: 'active' | 'inactive' | 'activating' | 'deactivating',
|
||||||
|
active: number,
|
||||||
|
inactive: number,
|
||||||
|
};
|
||||||
|
|
||||||
declare export type ParsedMessageAccount = {
|
declare export type ParsedMessageAccount = {
|
||||||
pubkey: PublicKey,
|
pubkey: PublicKey,
|
||||||
signer: boolean,
|
signer: boolean,
|
||||||
|
@ -328,6 +334,11 @@ declare module '@solana/web3.js' {
|
||||||
): Promise<
|
): Promise<
|
||||||
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>,
|
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>,
|
||||||
>;
|
>;
|
||||||
|
getStakeActivation(
|
||||||
|
publicKey: PublicKey,
|
||||||
|
commitment?: Commitment,
|
||||||
|
epoch?: number,
|
||||||
|
): Promise<StakeActivationData>;
|
||||||
getProgramAccounts(
|
getProgramAccounts(
|
||||||
programId: PublicKey,
|
programId: PublicKey,
|
||||||
commitment: ?Commitment,
|
commitment: ?Commitment,
|
||||||
|
|
|
@ -806,6 +806,20 @@ const ParsedAccountInfoResult = struct.object({
|
||||||
rentEpoch: 'number?',
|
rentEpoch: 'number?',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @private
|
||||||
|
*/
|
||||||
|
const StakeActivationResult = struct.object({
|
||||||
|
state: struct.union([
|
||||||
|
struct.literal('active'),
|
||||||
|
struct.literal('inactive'),
|
||||||
|
struct.literal('activating'),
|
||||||
|
struct.literal('deactivating'),
|
||||||
|
]),
|
||||||
|
active: 'number',
|
||||||
|
inactive: 'number',
|
||||||
|
});
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expected JSON RPC response for the "getAccountInfo" message
|
* Expected JSON RPC response for the "getAccountInfo" message
|
||||||
*/
|
*/
|
||||||
|
@ -820,6 +834,11 @@ const GetParsedAccountInfoResult = jsonRpcResultAndContext(
|
||||||
struct.union(['null', ParsedAccountInfoResult]),
|
struct.union(['null', ParsedAccountInfoResult]),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Expected JSON RPC response for the "getStakeActivation" message with jsonParsed param
|
||||||
|
*/
|
||||||
|
const GetStakeActivationResult = jsonRpcResult(StakeActivationResult);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Expected JSON RPC response for the "getConfirmedSignaturesForAddress" message
|
* Expected JSON RPC response for the "getConfirmedSignaturesForAddress" message
|
||||||
*/
|
*/
|
||||||
|
@ -1201,6 +1220,20 @@ type ParsedAccountData = {
|
||||||
space: number,
|
space: number,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stake Activation data
|
||||||
|
*
|
||||||
|
* @typedef {Object} StakeActivationData
|
||||||
|
* @property {string} state: <string - the stake account's activation state, one of: active, inactive, activating, deactivating
|
||||||
|
* @property {number} active: stake active during the epoch
|
||||||
|
* @property {number} inactive: stake inactive during the epoch
|
||||||
|
*/
|
||||||
|
type StakeActivationData = {
|
||||||
|
state: 'active' | 'inactive' | 'activating' | 'deactivating',
|
||||||
|
active: number,
|
||||||
|
inactive: number,
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information describing an account
|
* Information describing an account
|
||||||
*
|
*
|
||||||
|
@ -1857,6 +1890,36 @@ export class Connection {
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns epoch activation information for a stake account that has been delegated
|
||||||
|
*/
|
||||||
|
async getStakeActivation(
|
||||||
|
publicKey: PublicKey,
|
||||||
|
commitment: ?Commitment,
|
||||||
|
epoch: ?number,
|
||||||
|
): Promise<StakeActivationData> {
|
||||||
|
const args = this._buildArgs(
|
||||||
|
[publicKey.toBase58()],
|
||||||
|
commitment,
|
||||||
|
undefined,
|
||||||
|
epoch !== undefined ? {epoch} : undefined,
|
||||||
|
);
|
||||||
|
|
||||||
|
const unsafeRes = await this._rpcRequest('getStakeActivation', args);
|
||||||
|
const res = GetStakeActivationResult(unsafeRes);
|
||||||
|
if (res.error) {
|
||||||
|
throw new Error(
|
||||||
|
`failed to get Stake Activation ${publicKey.toBase58()}: ${
|
||||||
|
res.error.message
|
||||||
|
}`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
assert(typeof res.result !== 'undefined');
|
||||||
|
|
||||||
|
const {state, active, inactive} = res.result;
|
||||||
|
return {state, active, inactive};
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Fetch all the accounts owned by the specified program id
|
* Fetch all the accounts owned by the specified program id
|
||||||
*
|
*
|
||||||
|
@ -3093,9 +3156,10 @@ export class Connection {
|
||||||
args: Array<any>,
|
args: Array<any>,
|
||||||
override: ?Commitment,
|
override: ?Commitment,
|
||||||
encoding?: 'jsonParsed' | 'base64',
|
encoding?: 'jsonParsed' | 'base64',
|
||||||
|
extra?: any,
|
||||||
): Array<any> {
|
): Array<any> {
|
||||||
const commitment = override || this._commitment;
|
const commitment = override || this._commitment;
|
||||||
if (commitment || encoding) {
|
if (commitment || encoding || extra) {
|
||||||
let options: any = {};
|
let options: any = {};
|
||||||
if (encoding) {
|
if (encoding) {
|
||||||
options.encoding = encoding;
|
options.encoding = encoding;
|
||||||
|
@ -3103,6 +3167,9 @@ export class Connection {
|
||||||
if (commitment) {
|
if (commitment) {
|
||||||
options.commitment = commitment;
|
options.commitment = commitment;
|
||||||
}
|
}
|
||||||
|
if (extra) {
|
||||||
|
options = Object.assign(options, extra);
|
||||||
|
}
|
||||||
args.push(options);
|
args.push(options);
|
||||||
}
|
}
|
||||||
return args;
|
return args;
|
||||||
|
|
|
@ -4,12 +4,15 @@ import {Token, u64} from '@solana/spl-token';
|
||||||
|
|
||||||
import {
|
import {
|
||||||
Account,
|
Account,
|
||||||
|
Authorized,
|
||||||
Connection,
|
Connection,
|
||||||
SystemProgram,
|
SystemProgram,
|
||||||
Transaction,
|
Transaction,
|
||||||
sendAndConfirmTransaction,
|
sendAndConfirmTransaction,
|
||||||
LAMPORTS_PER_SOL,
|
LAMPORTS_PER_SOL,
|
||||||
|
Lockup,
|
||||||
PublicKey,
|
PublicKey,
|
||||||
|
StakeProgram,
|
||||||
} from '../src';
|
} from '../src';
|
||||||
import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing';
|
import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing';
|
||||||
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
||||||
|
@ -1521,6 +1524,125 @@ test('get largest accounts', async () => {
|
||||||
expect(largestAccounts.length).toEqual(20);
|
expect(largestAccounts.length).toEqual(20);
|
||||||
});
|
});
|
||||||
|
|
||||||
|
test('stake activation should throw when called for not delegated account', async () => {
|
||||||
|
const connection = new Connection(url);
|
||||||
|
|
||||||
|
const publicKey = new Account().publicKey;
|
||||||
|
mockRpc.push([
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'getStakeActivation',
|
||||||
|
params: [publicKey.toBase58(), {}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: {message: 'account not delegated'},
|
||||||
|
result: undefined,
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
|
||||||
|
await expect(connection.getStakeActivation(publicKey)).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stake activation should return activating for new accounts', 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 authorized = new Account();
|
||||||
|
await connection.requestAirdrop(authorized.publicKey, 2 * LAMPORTS_PER_SOL);
|
||||||
|
|
||||||
|
const minimumAmount = await connection.getMinimumBalanceForRentExemption(
|
||||||
|
StakeProgram.space,
|
||||||
|
'recent',
|
||||||
|
);
|
||||||
|
|
||||||
|
const newStakeAccount = new Account();
|
||||||
|
let createAndInitialize = StakeProgram.createAccount({
|
||||||
|
fromPubkey: authorized.publicKey,
|
||||||
|
stakePubkey: newStakeAccount.publicKey,
|
||||||
|
authorized: new Authorized(authorized.publicKey, authorized.publicKey),
|
||||||
|
lockup: new Lockup(0, 0, new PublicKey(0)),
|
||||||
|
lamports: minimumAmount + 42,
|
||||||
|
});
|
||||||
|
|
||||||
|
await sendAndConfirmTransaction(
|
||||||
|
connection,
|
||||||
|
createAndInitialize,
|
||||||
|
[authorized, newStakeAccount],
|
||||||
|
{commitment: 'single', skipPreflight: true},
|
||||||
|
);
|
||||||
|
let delegation = StakeProgram.delegate({
|
||||||
|
stakePubkey: newStakeAccount.publicKey,
|
||||||
|
authorizedPubkey: authorized.publicKey,
|
||||||
|
votePubkey,
|
||||||
|
});
|
||||||
|
await sendAndConfirmTransaction(connection, delegation, [authorized], {
|
||||||
|
commitment: 'single',
|
||||||
|
skipPreflight: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
const LARGE_EPOCH = 4000;
|
||||||
|
await expect(
|
||||||
|
connection.getStakeActivation(
|
||||||
|
newStakeAccount.publicKey,
|
||||||
|
'recent',
|
||||||
|
LARGE_EPOCH,
|
||||||
|
),
|
||||||
|
).rejects.toThrow(
|
||||||
|
`failed to get Stake Activation ${newStakeAccount.publicKey.toBase58()}: Invalid param: epoch ${LARGE_EPOCH} has not yet started`,
|
||||||
|
);
|
||||||
|
|
||||||
|
const activationState = await connection.getStakeActivation(
|
||||||
|
newStakeAccount.publicKey,
|
||||||
|
);
|
||||||
|
expect(activationState.state).toBe('activating');
|
||||||
|
expect(activationState.inactive).toBe(42);
|
||||||
|
expect(activationState.active).toBe(0);
|
||||||
|
});
|
||||||
|
|
||||||
|
test('stake activation should only accept state with valid string literals', async () => {
|
||||||
|
if (!mockRpcEnabled) {
|
||||||
|
console.log('live test skipped');
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
const connection = new Connection(url, 'recent');
|
||||||
|
const publicKey = new Account().publicKey;
|
||||||
|
|
||||||
|
const addStakeActivationMock = state => {
|
||||||
|
mockRpc.push([
|
||||||
|
url,
|
||||||
|
{
|
||||||
|
method: 'getStakeActivation',
|
||||||
|
params: [publicKey.toBase58(), {}],
|
||||||
|
},
|
||||||
|
{
|
||||||
|
error: undefined,
|
||||||
|
result: {
|
||||||
|
state: state,
|
||||||
|
active: 0,
|
||||||
|
inactive: 80,
|
||||||
|
},
|
||||||
|
},
|
||||||
|
]);
|
||||||
|
};
|
||||||
|
|
||||||
|
addStakeActivationMock('active');
|
||||||
|
let activation = await connection.getStakeActivation(publicKey);
|
||||||
|
expect(activation.state).toBe('active');
|
||||||
|
expect(activation.active).toBe(0);
|
||||||
|
expect(activation.inactive).toBe(80);
|
||||||
|
|
||||||
|
addStakeActivationMock('invalid');
|
||||||
|
await expect(connection.getStakeActivation(publicKey)).rejects.toThrow();
|
||||||
|
});
|
||||||
|
|
||||||
test('getVersion', async () => {
|
test('getVersion', async () => {
|
||||||
const connection = new Connection(url);
|
const connection = new Connection(url);
|
||||||
|
|
||||||
|
|
Loading…
Reference in New Issue