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;
|
||||
};
|
||||
|
||||
export type StakeActivationData = {
|
||||
state: 'active' | 'inactive' | 'activating' | 'deactivating';
|
||||
active: number;
|
||||
inactive: number;
|
||||
};
|
||||
|
||||
export type KeyedAccountInfo = {
|
||||
accountId: PublicKey;
|
||||
accountInfo: AccountInfo<Buffer>;
|
||||
|
@ -314,6 +320,11 @@ declare module '@solana/web3.js' {
|
|||
): Promise<
|
||||
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>
|
||||
>;
|
||||
getStakeActivation(
|
||||
publicKey: PublicKey,
|
||||
commitment?: Commitment,
|
||||
epoch?: number,
|
||||
): Promise<StakeActivationData>;
|
||||
getProgramAccounts(
|
||||
programId: PublicKey,
|
||||
commitment?: Commitment,
|
||||
|
|
|
@ -178,6 +178,12 @@ declare module '@solana/web3.js' {
|
|||
space: number,
|
||||
};
|
||||
|
||||
declare export type StakeActivationData = {
|
||||
state: 'active' | 'inactive' | 'activating' | 'deactivating',
|
||||
active: number,
|
||||
inactive: number,
|
||||
};
|
||||
|
||||
declare export type ParsedMessageAccount = {
|
||||
pubkey: PublicKey,
|
||||
signer: boolean,
|
||||
|
@ -328,6 +334,11 @@ declare module '@solana/web3.js' {
|
|||
): Promise<
|
||||
RpcResponseAndContext<AccountInfo<Buffer | ParsedAccountData> | null>,
|
||||
>;
|
||||
getStakeActivation(
|
||||
publicKey: PublicKey,
|
||||
commitment?: Commitment,
|
||||
epoch?: number,
|
||||
): Promise<StakeActivationData>;
|
||||
getProgramAccounts(
|
||||
programId: PublicKey,
|
||||
commitment: ?Commitment,
|
||||
|
|
|
@ -806,6 +806,20 @@ const ParsedAccountInfoResult = struct.object({
|
|||
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
|
||||
*/
|
||||
|
@ -820,6 +834,11 @@ const GetParsedAccountInfoResult = jsonRpcResultAndContext(
|
|||
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
|
||||
*/
|
||||
|
@ -1201,6 +1220,20 @@ type ParsedAccountData = {
|
|||
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
|
||||
*
|
||||
|
@ -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
|
||||
*
|
||||
|
@ -3093,9 +3156,10 @@ export class Connection {
|
|||
args: Array<any>,
|
||||
override: ?Commitment,
|
||||
encoding?: 'jsonParsed' | 'base64',
|
||||
extra?: any,
|
||||
): Array<any> {
|
||||
const commitment = override || this._commitment;
|
||||
if (commitment || encoding) {
|
||||
if (commitment || encoding || extra) {
|
||||
let options: any = {};
|
||||
if (encoding) {
|
||||
options.encoding = encoding;
|
||||
|
@ -3103,6 +3167,9 @@ export class Connection {
|
|||
if (commitment) {
|
||||
options.commitment = commitment;
|
||||
}
|
||||
if (extra) {
|
||||
options = Object.assign(options, extra);
|
||||
}
|
||||
args.push(options);
|
||||
}
|
||||
return args;
|
||||
|
|
|
@ -4,12 +4,15 @@ import {Token, u64} from '@solana/spl-token';
|
|||
|
||||
import {
|
||||
Account,
|
||||
Authorized,
|
||||
Connection,
|
||||
SystemProgram,
|
||||
Transaction,
|
||||
sendAndConfirmTransaction,
|
||||
LAMPORTS_PER_SOL,
|
||||
Lockup,
|
||||
PublicKey,
|
||||
StakeProgram,
|
||||
} from '../src';
|
||||
import {DEFAULT_TICKS_PER_SLOT, NUM_TICKS_PER_SECOND} from '../src/timing';
|
||||
import {mockRpc, mockRpcEnabled} from './__mocks__/node-fetch';
|
||||
|
@ -1521,6 +1524,125 @@ test('get largest accounts', async () => {
|
|||
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 () => {
|
||||
const connection = new Connection(url);
|
||||
|
||||
|
|
Loading…
Reference in New Issue