Add support for parsed stake accounts (#11469)

This commit is contained in:
Justin Starry 2020-08-08 21:06:24 +08:00 committed by GitHub
parent 102d15f081
commit c544116cf2
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 340 additions and 80 deletions

View File

@ -6,4 +6,5 @@ branches:
- master
script:
- npm run build
- npm run test
- npm run format

View File

@ -2843,6 +2843,14 @@
"@babel/types": "^7.3.0"
}
},
"@types/bn.js": {
"version": "4.11.6",
"resolved": "https://registry.npmjs.org/@types/bn.js/-/bn.js-4.11.6.tgz",
"integrity": "sha512-pqr857jrp2kPuO9uRjZ3PwnJTjoQy+fcdxvBTvHm6dkmEL9q+hDD/2j/0ELOBPtPnS8LjCX0gI9nbl8lVkadpg==",
"requires": {
"@types/node": "*"
}
},
"@types/bs58": {
"version": "4.0.1",
"resolved": "https://registry.npmjs.org/@types/bs58/-/bs58-4.0.1.tgz",
@ -2851,6 +2859,11 @@
"base-x": "^3.0.6"
}
},
"@types/chai": {
"version": "4.2.12",
"resolved": "https://registry.npmjs.org/@types/chai/-/chai-4.2.12.tgz",
"integrity": "sha512-aN5IAC8QNtSUdQzxu7lGBgYAOuU1tmRU4c9dIq5OKGf/SBVjXo+ffM2wEjudAWbgpOhy60nLoAGH1xm8fpCKFQ=="
},
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
@ -3791,6 +3804,11 @@
"resolved": "https://registry.npmjs.org/assert-plus/-/assert-plus-1.0.0.tgz",
"integrity": "sha1-8S4PPF13sLHN2RRpQuTpbB5N1SU="
},
"assertion-error": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/assertion-error/-/assertion-error-1.1.0.tgz",
"integrity": "sha512-jgsaNduz+ndvGyFt3uSuWqvy4lCnIJiovtouQN5JZHOKCS2QuhEdbcQHFhVksz2N2U9hXJo8odG7ETyWlEeuDw=="
},
"assign-symbols": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/assign-symbols/-/assign-symbols-1.0.0.tgz",
@ -5087,6 +5105,34 @@
"resolved": "https://registry.npmjs.org/caseless/-/caseless-0.12.0.tgz",
"integrity": "sha1-G2gcIf+EAzyCZUMJBolCDRhxUdw="
},
"chai": {
"version": "4.2.0",
"resolved": "https://registry.npmjs.org/chai/-/chai-4.2.0.tgz",
"integrity": "sha512-XQU3bhBukrOsQCuwZndwGcCVQHyZi53fQ6Ys1Fym7E4olpIqqZZhhoFJoaKVvV17lWQoXYwgWN2nF5crA8J2jw==",
"requires": {
"assertion-error": "^1.1.0",
"check-error": "^1.0.2",
"deep-eql": "^3.0.1",
"get-func-name": "^2.0.0",
"pathval": "^1.1.0",
"type-detect": "^4.0.5"
},
"dependencies": {
"deep-eql": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/deep-eql/-/deep-eql-3.0.1.tgz",
"integrity": "sha512-+QeIQyN5ZuO+3Uk5DYh6/1eKO0m0YmJFGNmFHGACpf1ClL1nmlV/p4gNgbl2pJGxgXb4faqo6UE+M5ACEMyVcw==",
"requires": {
"type-detect": "^4.0.0"
}
},
"type-detect": {
"version": "4.0.8",
"resolved": "https://registry.npmjs.org/type-detect/-/type-detect-4.0.8.tgz",
"integrity": "sha512-0fr/mIH1dlO+x7TlcMy+bIDqKPsw/70tVyeHW787goQjhmqaZe10uwLujubK9q9Lg6Fiho1KUKDYz0Z7k7g5/g=="
}
}
},
"chalk": {
"version": "2.4.2",
"resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
@ -5102,6 +5148,11 @@
"resolved": "https://registry.npmjs.org/chardet/-/chardet-0.7.0.tgz",
"integrity": "sha512-mT8iDcrh03qDGRRmoA2hmBJnxpllMR+0/0qlzjqZES6NdiWDcZkCNAk4rPFZ9Q85r27unkiNNg8ZOiwZXBHwcA=="
},
"check-error": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/check-error/-/check-error-1.0.2.tgz",
"integrity": "sha1-V00xLt2Iu13YkS6Sht1sCu1KrII="
},
"cheerio": {
"version": "0.22.0",
"resolved": "https://registry.npmjs.org/cheerio/-/cheerio-0.22.0.tgz",
@ -7882,6 +7933,11 @@
"resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-1.0.3.tgz",
"integrity": "sha512-3t6rVToeoZfYSGd8YoLFR2DJkiQrIiUrGcjvFX2mDw3bn6k2OtwHN0TNCLbBO+w8qTvimhDkv+LSscbJY1vE6w=="
},
"get-func-name": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/get-func-name/-/get-func-name-2.0.0.tgz",
"integrity": "sha1-6td0q+5y4gQJQzoGY2YCPdaIekE="
},
"get-own-enumerable-property-symbols": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/get-own-enumerable-property-symbols/-/get-own-enumerable-property-symbols-3.0.2.tgz",
@ -11737,6 +11793,11 @@
"pinkie-promise": "^2.0.0"
}
},
"pathval": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/pathval/-/pathval-1.1.0.tgz",
"integrity": "sha1-uULm1L3mUwBe9rcTYd74cn0GReA="
},
"pbkdf2": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/pbkdf2/-/pbkdf2-3.1.1.tgz",

View File

@ -8,7 +8,9 @@
"@testing-library/jest-dom": "^5.11.2",
"@testing-library/react": "^10.4.8",
"@testing-library/user-event": "^12.1.0",
"@types/bn.js": "^4.11.6",
"@types/bs58": "^4.0.1",
"@types/chai": "^4.2.12",
"@types/jest": "^26.0.9",
"@types/node": "^14.0.27",
"@types/react": "^16.9.44",
@ -16,8 +18,10 @@
"@types/react-router-dom": "^5.1.5",
"@types/react-select": "^3.0.16",
"@types/socket.io-client": "^1.4.33",
"bn.js": "^5.1.2",
"bootstrap": "^4.5.1",
"bs58": "^4.0.1",
"chai": "^4.2.0",
"humanize-duration-ts": "^2.1.1",
"node-sass": "^4.14.1",
"prettier": "^2.0.5",

View File

@ -0,0 +1,28 @@
import { expect } from "chai";
import { lamportsToSol, LAMPORTS_PER_SOL } from "utils";
import BN from "bn.js";
describe("lamportsToSol", () => {
it("0 lamports", () => {
expect(lamportsToSol(new BN(0))).to.eq(0.0);
});
it("1 lamport", () => {
expect(lamportsToSol(new BN(1))).to.eq(0.000000001);
expect(lamportsToSol(new BN(-1))).to.eq(-0.000000001);
});
it("1 SOL", () => {
expect(lamportsToSol(new BN(LAMPORTS_PER_SOL))).to.eq(1.0);
expect(lamportsToSol(new BN(-LAMPORTS_PER_SOL))).to.eq(-1.0);
});
it("u64::MAX lamports", () => {
expect(lamportsToSol(new BN(2).pow(new BN(64)))).to.eq(
18446744073.709551615
);
expect(lamportsToSol(new BN(2).pow(new BN(64)).neg())).to.eq(
-18446744073.709551615
);
});
});

View File

@ -1,29 +1,54 @@
import React from "react";
import { StakeAccount, Meta } from "solana-sdk-wasm";
import { StakeAccount as StakeAccountWasm, Meta } from "solana-sdk-wasm";
import { TableCardBody } from "components/common/TableCardBody";
import { lamportsToSolString } from "utils";
import { displayTimestamp } from "utils/date";
import { Account, useFetchAccountInfo } from "providers/accounts";
import { Address } from "components/common/Address";
import {
StakeAccountInfo,
StakeMeta,
StakeAccountType,
} from "providers/accounts/types";
import BN from "bn.js";
const MAX_EPOCH = new BN(2).pow(new BN(64));
export function StakeAccountSection({
account,
stakeAccount,
stakeAccountType,
}: {
account: Account;
stakeAccount: StakeAccount;
stakeAccount: StakeAccountInfo | StakeAccountWasm;
stakeAccountType: StakeAccountType;
}) {
return (
<>
<LockupCard stakeAccount={stakeAccount} />
<OverviewCard account={account} stakeAccount={stakeAccount} />
{stakeAccount.meta && <DelegationCard stakeAccount={stakeAccount} />}
{stakeAccount.meta && <AuthoritiesCard meta={stakeAccount.meta} />}
<OverviewCard
account={account}
stakeAccount={stakeAccount}
stakeAccountType={stakeAccountType}
/>
{stakeAccount.meta && (
<>
<DelegationCard
stakeAccount={stakeAccount}
stakeAccountType={stakeAccountType}
/>
<AuthoritiesCard meta={stakeAccount.meta} />
</>
)}
</>
);
}
function LockupCard({ stakeAccount }: { stakeAccount: StakeAccount }) {
function LockupCard({
stakeAccount,
}: {
stakeAccount: StakeAccountInfo | StakeAccountWasm;
}) {
const unixTimestamp = stakeAccount.meta?.lockup.unixTimestamp;
if (unixTimestamp && unixTimestamp > 0) {
const prettyTimestamp = displayTimestamp(unixTimestamp * 1000);
@ -37,12 +62,21 @@ function LockupCard({ stakeAccount }: { stakeAccount: StakeAccount }) {
}
}
const TYPE_NAMES = {
uninitialized: "Uninitialized",
initialized: "Initialized",
delegated: "Delegated",
rewardsPool: "RewardsPool",
};
function OverviewCard({
account,
stakeAccount,
stakeAccountType,
}: {
account: Account;
stakeAccount: StakeAccount;
stakeAccount: StakeAccountInfo | StakeAccountWasm;
stakeAccountType: StakeAccountType;
}) {
const refresh = useFetchAccountInfo();
return (
@ -84,7 +118,7 @@ function OverviewCard({
{!stakeAccount.meta && (
<tr>
<td>State</td>
<td className="text-lg-right">{stakeAccount.displayState()}</td>
<td className="text-lg-right">{TYPE_NAMES[stakeAccountType]}</td>
</tr>
)}
</TableCardBody>
@ -92,16 +126,48 @@ function OverviewCard({
);
}
function DelegationCard({ stakeAccount }: { stakeAccount: StakeAccount }) {
const { stake } = stakeAccount;
function DelegationCard({
stakeAccount,
stakeAccountType,
}: {
stakeAccount: StakeAccountInfo | StakeAccountWasm;
stakeAccountType: StakeAccountType;
}) {
const displayStatus = () => {
let status = stakeAccount.displayState();
if (status !== "Delegated") {
// TODO check epoch
let status = TYPE_NAMES[stakeAccountType];
if (stakeAccountType !== "delegated") {
status = "Not delegated";
}
return status;
};
let voterPubkey, activationEpoch, deactivationEpoch;
if ("accountType" in stakeAccount) {
const delegation = stakeAccount?.stake?.delegation;
if (delegation) {
voterPubkey = delegation.voterPubkey;
activationEpoch = delegation.isBootstrapStake()
? "-"
: delegation.activationEpoch;
deactivationEpoch = delegation.isDeactivated()
? delegation.deactivationEpoch
: "-";
}
} else {
const delegation = stakeAccount?.stake?.delegation;
if (delegation) {
voterPubkey = delegation.voter;
activationEpoch = delegation.activationEpoch.eq(MAX_EPOCH)
? "-"
: delegation.activationEpoch.toString();
deactivationEpoch = delegation.deactivationEpoch.eq(MAX_EPOCH)
? "-"
: delegation.deactivationEpoch.toString();
}
}
const { stake } = stakeAccount;
return (
<div className="card">
<div className="card-header">
@ -124,33 +190,23 @@ function DelegationCard({ stakeAccount }: { stakeAccount: StakeAccount }) {
</td>
</tr>
<tr>
<td>Delegated Vote Address</td>
<td className="text-lg-right">
<Address
pubkey={stake.delegation.voterPubkey}
alignRight
link
/>
</td>
</tr>
{voterPubkey && (
<tr>
<td>Delegated Vote Address</td>
<td className="text-lg-right">
<Address pubkey={voterPubkey} alignRight link />
</td>
</tr>
)}
<tr>
<td>Activation Epoch</td>
<td className="text-lg-right">
{stake.delegation.isBootstrapStake()
? "-"
: stake.delegation.activationEpoch}
</td>
<td className="text-lg-right">{activationEpoch}</td>
</tr>
<tr>
<td>Deactivation Epoch</td>
<td className="text-lg-right">
{stake.delegation.isDeactivated()
? stake.delegation.deactivationEpoch
: "-"}
</td>
<td className="text-lg-right">{deactivationEpoch}</td>
</tr>
</>
)}
@ -159,7 +215,7 @@ function DelegationCard({ stakeAccount }: { stakeAccount: StakeAccount }) {
);
}
function AuthoritiesCard({ meta }: { meta: Meta }) {
function AuthoritiesCard({ meta }: { meta: Meta | StakeMeta }) {
const hasLockup = meta && meta.lockup.unixTimestamp > 0;
return (
<div className="card">

View File

@ -28,7 +28,7 @@ export function UnknownAccountCard({ account }: { account: Account }) {
</td>
</tr>
{details && (
{details?.space !== undefined && (
<tr>
<td>Data (Bytes)</td>
<td className="text-lg-right">{details.space}</td>

View File

@ -10,7 +10,8 @@ import {
import { UnknownDetailsCard } from "../UnknownDetailsCard";
import { InstructionCard } from "../InstructionCard";
import { Address } from "components/common/Address";
import { ParsedInstructionInfo, IX_STRUCTS } from "./types";
import { IX_STRUCTS, TokenInstructionType } from "./types";
import { ParsedInfo } from "validators";
const IX_TITLES = {
initializeMint: "Initialize Mint",
@ -34,8 +35,9 @@ type DetailsProps = {
export function TokenDetailsCard(props: DetailsProps) {
try {
const parsed = coerce(props.ix.parsed, ParsedInstructionInfo);
const { type, info } = parsed;
const parsed = coerce(props.ix.parsed, ParsedInfo);
const { type: rawType, info } = parsed;
const type = coerce(rawType, TokenInstructionType);
const title = `Token: ${IX_TITLES[type]}`;
const coerced = coerce(info, IX_STRUCTS[type] as any);
return <TokenInstruction title={title} info={coerced} {...props} />;

View File

@ -1,21 +1,12 @@
import {
enums,
object,
any,
StructType,
coercion,
struct,
number,
optional,
array,
} from "superstruct";
import { PublicKey } from "@solana/web3.js";
const PubkeyValue = struct("Pubkey", (value) => value instanceof PublicKey);
const Pubkey = coercion(PubkeyValue, (value) => {
if (typeof value === "string") return new PublicKey(value);
throw new Error("invalid pubkey");
});
import { Pubkey } from "validators/pubkey";
const InitializeMint = object({
mint: Pubkey,
@ -95,8 +86,8 @@ const CloseAccount = object({
signers: optional(array(Pubkey)),
});
type TokenInstructionType = StructType<typeof TokenInstructionType>;
const TokenInstructionType = enums([
export type TokenInstructionType = StructType<typeof TokenInstructionType>;
export const TokenInstructionType = enums([
"initializeMint",
"initializeAccount",
"initializeMultisig",
@ -121,9 +112,3 @@ export const IX_STRUCTS = {
burn: Burn,
closeAccount: CloseAccount,
};
export type ParsedInstructionInfo = StructType<typeof ParsedInstructionInfo>;
export const ParsedInstructionInfo = object({
type: TokenInstructionType,
info: any(),
});

View File

@ -68,7 +68,22 @@ function InfoSection({ pubkey }: { pubkey: PublicKey }) {
const owner = info.details?.owner;
const data = info.details?.data;
if (data && owner && owner.equals(StakeProgram.programId)) {
return <StakeAccountSection account={info} stakeAccount={data} />;
let stakeAccountType, stakeAccount;
if ("accountType" in data) {
stakeAccount = data;
stakeAccountType = data.accountType as any;
} else {
stakeAccount = data.info;
stakeAccountType = data.type;
}
return (
<StakeAccountSection
account={info}
stakeAccount={stakeAccount}
stakeAccountType={stakeAccountType}
/>
);
} else {
return <UnknownAccountCard account={info} />;
}

View File

@ -1,9 +1,12 @@
import React from "react";
import { StakeAccount } from "solana-sdk-wasm";
import { StakeAccount as StakeAccountWasm } from "solana-sdk-wasm";
import { PublicKey, Connection, StakeProgram } from "@solana/web3.js";
import { useCluster } from "../cluster";
import { HistoryProvider } from "./history";
import { TokensProvider } from "./tokens";
import { coerce } from "superstruct";
import { ParsedInfo } from "validators";
import { StakeAccount } from "./types";
export { useAccountHistory } from "./history";
export enum FetchStatus {
@ -15,8 +18,8 @@ export enum FetchStatus {
export interface Details {
executable: boolean;
owner: PublicKey;
space: number;
data?: StakeAccount;
space?: number;
data?: StakeAccount | StakeAccountWasm;
}
export interface Account {
@ -156,18 +159,30 @@ async function fetchAccountInfo(
let details;
let lamports;
try {
const result = await new Connection(url, "recent").getAccountInfo(pubkey);
const result = (
await new Connection(url, "single").getParsedAccountInfo(pubkey)
).value;
if (result === null) {
lamports = 0;
} else {
lamports = result.lamports;
let data = undefined;
// Only save data in memory if we can decode it
let space;
if (!("parsed" in result.data)) {
space = result.data.length;
}
let data;
if (result.owner.equals(StakeProgram.programId)) {
try {
const wasm = await import("solana-sdk-wasm");
data = wasm.StakeAccount.fromAccountData(result.data);
if ("parsed" in result.data) {
const info = coerce(result.data.parsed, ParsedInfo);
data = coerce(info, StakeAccount);
} else {
const wasm = await import("solana-sdk-wasm");
data = wasm.StakeAccount.fromAccountData(result.data);
}
} catch (err) {
console.error("Unexpected error loading wasm", err);
// TODO store error state in Account info
@ -175,7 +190,7 @@ async function fetchAccountInfo(
}
details = {
space: result.data.length,
space,
executable: result.executable,
owner: result.owner,
data,

View File

@ -0,0 +1,48 @@
import { object, StructType, number, optional, enums } from "superstruct";
import { Pubkey } from "validators/pubkey";
import { BigNum } from "validators/bignum";
export type StakeAccountType = StructType<typeof StakeAccountType>;
export const StakeAccountType = enums([
"uninitialized",
"initialized",
"delegated",
"rewardsPool",
]);
export type StakeMeta = StructType<typeof StakeMeta>;
export const StakeMeta = object({
rentExemptReserve: BigNum,
authorized: object({
staker: Pubkey,
withdrawer: Pubkey,
}),
lockup: object({
unixTimestamp: number(),
epoch: number(),
custodian: Pubkey,
}),
});
export type StakeAccountInfo = StructType<typeof StakeAccountInfo>;
export const StakeAccountInfo = object({
meta: StakeMeta,
stake: optional(
object({
delegation: object({
voter: Pubkey,
stake: BigNum,
activationEpoch: BigNum,
deactivationEpoch: BigNum,
warmupCooldownRate: number(),
}),
creditsObserved: number(),
})
),
});
export type StakeAccount = StructType<typeof StakeAccount>;
export const StakeAccount = object({
type: StakeAccountType,
info: StakeAccountInfo,
});

View File

@ -1,10 +1,12 @@
import React from "react";
import { LAMPORTS_PER_SOL } from "@solana/web3.js";
import React, { ReactNode } from "react";
import BN from "bn.js";
import {
HumanizeDuration,
HumanizeDurationLanguage,
} from "humanize-duration-ts";
import { ReactNode } from "react";
// Switch to web3 constant when web3 updates superstruct
export const LAMPORTS_PER_SOL = 1000000000;
export const NUM_TICKS_PER_SECOND = 160;
export const DEFAULT_TICKS_PER_SLOT = 64;
@ -16,11 +18,31 @@ export function assertUnreachable(x: never): never {
throw new Error("Unreachable!");
}
export function lamportsToSol(lamports: number | BN): number {
if (typeof lamports === "number") {
return Math.abs(lamports) / LAMPORTS_PER_SOL;
}
let signMultiplier = 1;
if (lamports.isNeg()) {
signMultiplier = -1;
}
const absLamports = lamports.abs();
const lamportsString = absLamports.toString(10).padStart(10, "0");
const splitIndex = lamportsString.length - 9;
const solString =
lamportsString.slice(0, splitIndex) +
"." +
lamportsString.slice(splitIndex);
return signMultiplier * parseFloat(solString);
}
export function lamportsToSolString(
lamports: number,
lamports: number | BN,
maximumFractionDigits: number = 9
): ReactNode {
const sol = Math.abs(lamports) / LAMPORTS_PER_SOL;
const sol = lamportsToSol(lamports);
return (
<>

View File

@ -0,0 +1,8 @@
import { coercion, struct, Struct } from "superstruct";
import BN from "bn.js";
const BigNumValue = struct("BigNum", (value) => value instanceof BN);
export const BigNum: Struct<BN, any> = coercion(BigNumValue, (value) => {
if (typeof value === "string") return new BN(value, 10);
throw new Error("invalid big num");
});

View File

@ -0,0 +1,7 @@
import { object, any, StructType, string } from "superstruct";
export type ParsedInfo = StructType<typeof ParsedInfo>;
export const ParsedInfo = object({
type: string(),
info: any(),
});

View File

@ -0,0 +1,8 @@
import { coercion, struct, Struct } from "superstruct";
import { PublicKey } from "@solana/web3.js";
const PubkeyValue = struct("Pubkey", (value) => value instanceof PublicKey);
export const Pubkey: Struct<PublicKey, any> = coercion(PubkeyValue, (value) => {
if (typeof value === "string") return new PublicKey(value);
throw new Error("invalid pubkey");
});

View File

@ -4,7 +4,7 @@
"Solana Maintainers <maintainers@solana.com>"
],
"description": "Solana SDK Wasm",
"version": "1.2.0",
"version": "1.4.0",
"license": "Apache-2.0",
"repository": {
"type": "git",

View File

@ -124,7 +124,7 @@ export class StakeAccount {
/**
* @returns {string}
*/
displayState(): string;
accountType(): string;
/**
* @returns {Meta | undefined}
*/

View File

@ -9,7 +9,7 @@ export function __wbg_set_stakeaccount_stake(a: number, b: number): void;
export function __wbg_get_stakeaccount_state(a: number): number;
export function __wbg_set_stakeaccount_state(a: number, b: number): void;
export function stakeaccount_fromAccountData(a: number, b: number): number;
export function stakeaccount_displayState(a: number, b: number): void;
export function stakeaccount_accountType(a: number, b: number): void;
export function __wbg_lockup_free(a: number): void;
export function __wbg_get_lockup_custodian(a: number): number;
export function __wbg_set_lockup_custodian(a: number, b: number): void;

View File

@ -456,9 +456,9 @@ export class StakeAccount {
/**
* @returns {string}
*/
displayState() {
accountType() {
try {
wasm.stakeaccount_displayState(8, this.ptr);
wasm.stakeaccount_accountType(8, this.ptr);
var r0 = getInt32Memory0()[8 / 4 + 0];
var r1 = getInt32Memory0()[8 / 4 + 1];
return getStringFromWasm0(r0, r1);

View File

@ -62,13 +62,13 @@ impl StakeAccount {
return Ok(stake_state.into());
}
#[wasm_bindgen(js_name = displayState)]
pub fn display_state(&self) -> String {
#[wasm_bindgen(js_name = accountType)]
pub fn account_type(&self) -> String {
match self.state {
State::Uninitialized => "Uninitialized".to_string(),
State::Initialized => "Initialized".to_string(),
State::Delegated => "Delegated".to_string(),
State::RewardsPool => "RewardsPool".to_string(),
State::Uninitialized => "uninitialized".to_string(),
State::Initialized => "initialized".to_string(),
State::Delegated => "delegated".to_string(),
State::RewardsPool => "rewardsPool".to_string(),
}
}
}