Skip to content
This repository has been archived by the owner on Jul 15, 2022. It is now read-only.

Commit

Permalink
Solana staking integration (#1825)
Browse files Browse the repository at this point in the history
* solana add initial staking support

* solana improve staking

* solana fix typos

* solana fix cli format for staking

* solana add device fields for stake create acc

* solana add stake delegate support

* solana fix lint

* solana add stake undelegate

* solana add stake withdraw

* solana add stake split

* solana add stakes loading

* solana improve staking

* solana introduce solana resources

* solana add preload data

* solana add hydrate to bridge

* solana add stake actions

* solana improve staking

* solana add meta to validators

* solana add validator name

* solana fix seed for stake accs

* solana add stake reward

* solana improve staking

* solana stake add withdrawable amount

* solana refactor framework

* solana fix tests

* solana make auto delegation mandatory

* solana sort stakes

* solana fix error key

* solana add delegation validator validation

* solana add staking create acc tests

* solana add staking delegate tests

* solana add stake undelegate tests

* solana remove redundant checks for stake delegation

* solana skip options validations on cli level

* solana skip undelegate options validations on cli level

* solana improve staking tests

* solana add delegatable check to staking

* solana fix estimate max spendable

* solana add delegation active test

* solana add stake state tests

* solana introduce validators app validators

* solana add validators for testnet & devnet

* solana refactor validators app validators

* solana update meta of stakes

* solana update sort order of stakes

* solana add delegated op type

* solana shuffle staking validators

* solana fix stake withdraw optimistic value

* solana clean comments

* solana update mock data

* solana add initial staking bot specs

* solana add staking bot specs

* solana move ledger vote acc to utils

* solana add swap util

* solana update device tx config

* solana fix lint
  • Loading branch information
konoart authored Apr 1, 2022
1 parent 39968b3 commit d70cf09
Show file tree
Hide file tree
Showing 41 changed files with 3,959 additions and 1,344 deletions.
6 changes: 6 additions & 0 deletions src/__tests__/__snapshots__/all.libcore.ts.snap
Original file line number Diff line number Diff line change
Expand Up @@ -29785,6 +29785,9 @@ Array [
"operationsCount": 2,
"pendingOperations": Array [],
"seedIdentifier": "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh",
"solanaResources": Object {
"stakes": "[]",
},
"spendableBalance": "83389840",
"starred": false,
"swapHistory": Array [],
Expand All @@ -29811,6 +29814,9 @@ Array [
"operationsCount": 0,
"pendingOperations": Array [],
"seedIdentifier": "AQbkEagmPgmsdAfS4X8V8UyJnXXjVPMvjeD15etqQ3Jh",
"solanaResources": Object {
"stakes": "[]",
},
"spendableBalance": "0",
"starred": false,
"swapHistory": Array [],
Expand Down
18 changes: 18 additions & 0 deletions src/account/serialization.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,12 @@ import {
toCryptoOrgResourcesRaw,
fromCryptoOrgResourcesRaw,
} from "../families/crypto_org/serialization";

import {
toSolanaResourcesRaw,
fromSolanaResourcesRaw,
} from "../families/solana/serialization";

import {
getCryptoCurrencyById,
getTokenById,
Expand All @@ -68,6 +74,7 @@ export { toPolkadotResourcesRaw, fromPolkadotResourcesRaw };
export { toTezosResourcesRaw, fromTezosResourcesRaw };
export { toElrondResourcesRaw, fromElrondResourcesRaw };
export { toCryptoOrgResourcesRaw, fromCryptoOrgResourcesRaw };
export { toSolanaResourcesRaw, fromSolanaResourcesRaw };

export function toBalanceHistoryRaw(b: BalanceHistory): BalanceHistoryRaw {
return b.map(({ date, value }) => [date.toISOString(), value.toString()]);
Expand Down Expand Up @@ -707,6 +714,7 @@ export function fromAccountRaw(rawAccount: AccountRaw): Account {
polkadotResources,
elrondResources,
cryptoOrgResources,
solanaResources,
nfts,
} = rawAccount;
const subAccounts =
Expand Down Expand Up @@ -828,6 +836,10 @@ export function fromAccountRaw(rawAccount: AccountRaw): Account {
res.cryptoOrgResources = fromCryptoOrgResourcesRaw(cryptoOrgResources);
}

if (solanaResources) {
res.solanaResources = fromSolanaResourcesRaw(solanaResources);
}

return res;
}
export function toAccountRaw({
Expand Down Expand Up @@ -866,6 +878,7 @@ export function toAccountRaw({
polkadotResources,
elrondResources,
cryptoOrgResources,
solanaResources,
nfts,
}: Account): AccountRaw {
const res: AccountRaw = {
Expand Down Expand Up @@ -946,6 +959,11 @@ export function toAccountRaw({
if (cryptoOrgResources) {
res.cryptoOrgResources = toCryptoOrgResourcesRaw(cryptoOrgResources);
}

if (solanaResources) {
res.solanaResources = toSolanaResourcesRaw(solanaResources);
}

return res;
}

Expand Down
40 changes: 40 additions & 0 deletions src/families/solana/api/cached.ts
Original file line number Diff line number Diff line change
Expand Up @@ -17,6 +17,8 @@ const cacheKeyAddress = (address: string) => address;
const cacheKeyEmpty = () => "" as const;
const cacheKeyAssocTokenAccAddress = (owner: string, mint: string) =>
`${owner}:${mint}`;
const cacheKeyMinimumBalanceForRentExemption = (dataLengt: number) =>
dataLengt.toString();

const cacheKeyTransactions = (signatures: string[]) =>
hash([...signatures].sort());
Expand Down Expand Up @@ -63,6 +65,36 @@ export function cached(api: ChainAPI): ChainAPI {
minutes(1)
),

getStakeAccountsByStakeAuth: makeLRUCache(
api.getStakeAccountsByStakeAuth,
cacheKeyAddress,
minutes(1)
),

getStakeAccountsByWithdrawAuth: makeLRUCache(
api.getStakeAccountsByWithdrawAuth,
cacheKeyAddress,
minutes(1)
),

getStakeActivation: makeLRUCache(
api.getStakeActivation,
cacheKeyAddress,
minutes(1)
),

getInflationReward: makeLRUCache(
api.getInflationReward,
cacheKeyByArgs,
minutes(5)
),

getVoteAccounts: makeLRUCache(
api.getVoteAccounts,
cacheKeyEmpty,
minutes(1)
),

getRecentBlockhash: makeLRUCache(
api.getRecentBlockhash,
cacheKeyEmpty,
Expand All @@ -81,9 +113,17 @@ export function cached(api: ChainAPI): ChainAPI {
seconds(30)
),

getMinimumBalanceForRentExemption: makeLRUCache(
api.getMinimumBalanceForRentExemption,
cacheKeyMinimumBalanceForRentExemption,
minutes(5)
),

// do not cache
sendRawTransaction: api.sendRawTransaction,

getEpochInfo: makeLRUCache(api.getEpochInfo, cacheKeyEmpty, minutes(1)),

config: api.config,
};
}
6 changes: 5 additions & 1 deletion src/families/solana/api/chain/account/index.ts
Original file line number Diff line number Diff line change
@@ -1 +1,5 @@
export { tryParseAsTokenAccount, parseTokenAccountInfo } from "./parser";
export {
tryParseAsTokenAccount,
parseTokenAccountInfo,
tryParseAsVoteAccount,
} from "./parser";
28 changes: 28 additions & 0 deletions src/families/solana/api/chain/account/parser.ts
Original file line number Diff line number Diff line change
@@ -1,7 +1,10 @@
import { ParsedAccountData } from "@solana/web3.js";
import { create } from "superstruct";
import { PARSED_PROGRAMS } from "../program/constants";
import { ParsedInfo } from "../validators";
import { StakeAccountInfo } from "./stake";
import { TokenAccount, TokenAccountInfo } from "./token";
import { VoteAccount, VoteAccountInfo } from "./vote";

export function parseTokenAccountInfo(info: unknown): TokenAccountInfo {
return create(info, TokenAccountInfo);
Expand All @@ -26,6 +29,31 @@ export function tryParseAsTokenAccount(
return onThrowReturnError(routine);
}

export function parseVoteAccountInfo(info: unknown): VoteAccountInfo {
return create(info, VoteAccountInfo);
}

export function tryParseAsVoteAccount(
data: ParsedAccountData
): VoteAccountInfo | undefined | Error {
const routine = () => {
const info = create(data.parsed, ParsedInfo);

if (data.program === PARSED_PROGRAMS.VOTE) {
const parsed = create(info, VoteAccount);
return parseVoteAccountInfo(parsed.info);
}

return undefined;
};

return onThrowReturnError(routine);
}

export function parseStakeAccountInfo(info: unknown): StakeAccountInfo {
return create(info, StakeAccountInfo);
}

function onThrowReturnError<R>(fn: () => R) {
try {
return fn();
Expand Down
63 changes: 63 additions & 0 deletions src/families/solana/api/chain/account/vote.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
/* eslint-disable @typescript-eslint/no-redeclare */

import {
Infer,
enums,
number,
array,
type,
nullable,
string,
} from "superstruct";
import { PublicKeyFromString } from "../validators/pubkey";

export type VoteAccountType = Infer<typeof VoteAccountType>;
export const VoteAccountType = enums(["vote"]);

export type AuthorizedVoter = Infer<typeof AuthorizedVoter>;
export const AuthorizedVoter = type({
authorizedVoter: PublicKeyFromString,
epoch: number(),
});

export type PriorVoter = Infer<typeof PriorVoter>;
export const PriorVoter = type({
authorizedPubkey: PublicKeyFromString,
epochOfLastAuthorizedSwitch: number(),
targetEpoch: number(),
});

export type EpochCredits = Infer<typeof EpochCredits>;
export const EpochCredits = type({
epoch: number(),
credits: string(),
previousCredits: string(),
});

export type Vote = Infer<typeof Vote>;
export const Vote = type({
slot: number(),
confirmationCount: number(),
});

export type VoteAccountInfo = Infer<typeof VoteAccountInfo>;
export const VoteAccountInfo = type({
authorizedVoters: array(AuthorizedVoter),
authorizedWithdrawer: PublicKeyFromString,
commission: number(),
epochCredits: array(EpochCredits),
lastTimestamp: type({
slot: number(),
timestamp: number(),
}),
nodePubkey: PublicKeyFromString,
priorVoters: array(PriorVoter),
rootSlot: nullable(number()),
votes: array(Vote),
});

export type VoteAccount = Infer<typeof VoteAccount>;
export const VoteAccount = type({
type: VoteAccountType,
info: VoteAccountInfo,
});
63 changes: 63 additions & 0 deletions src/families/solana/api/chain/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import {
PublicKey,
sendAndConfirmRawTransaction,
SignaturesForAddressOptions,
StakeProgram,
} from "@solana/web3.js";
import { Awaited } from "../../logic";

Expand All @@ -32,6 +33,24 @@ export type ChainAPI = Readonly<{
address: string
) => ReturnType<Connection["getParsedTokenAccountsByOwner"]>;

getStakeAccountsByStakeAuth: (
authAddr: string
) => ReturnType<Connection["getParsedProgramAccounts"]>;

getStakeAccountsByWithdrawAuth: (
authAddr: string
) => ReturnType<Connection["getParsedProgramAccounts"]>;

getStakeActivation: (
stakeAccAddr: string
) => ReturnType<Connection["getStakeActivation"]>;

getInflationReward: (
addresses: string[]
) => ReturnType<Connection["getInflationReward"]>;

getVoteAccounts: () => ReturnType<Connection["getVoteAccounts"]>;

getSignaturesForAddress: (
address: string,
opts?: SignaturesForAddressOptions
Expand All @@ -55,6 +74,10 @@ export type ChainAPI = Readonly<{

getAssocTokenAccMinNativeBalance: () => Promise<number>;

getMinimumBalanceForRentExemption: (dataLength: number) => Promise<number>;

getEpochInfo: () => ReturnType<Connection["getEpochInfo"]>;

config: Config;
}>;

Expand Down Expand Up @@ -98,6 +121,41 @@ export function getChainAPI(
connection().getParsedTokenAccountsByOwner(new PublicKey(address), {
programId: TOKEN_PROGRAM_ID,
}),

getStakeAccountsByStakeAuth: (authAddr: string) =>
connection().getParsedProgramAccounts(StakeProgram.programId, {
filters: [
{
memcmp: {
offset: 12,
bytes: authAddr,
},
},
],
}),

getStakeAccountsByWithdrawAuth: (authAddr: string) =>
connection().getParsedProgramAccounts(StakeProgram.programId, {
filters: [
{
memcmp: {
offset: 44,
bytes: authAddr,
},
},
],
}),

getStakeActivation: (stakeAccAddr: string) =>
connection().getStakeActivation(new PublicKey(stakeAccAddr)),

getInflationReward: (addresses: string[]) =>
connection().getInflationReward(
addresses.map((addr) => new PublicKey(addr))
),

getVoteAccounts: () => connection().getVoteAccounts(),

getSignaturesForAddress: (
address: string,
opts?: SignaturesForAddressOptions
Expand Down Expand Up @@ -128,6 +186,11 @@ export function getChainAPI(
getAssocTokenAccMinNativeBalance: () =>
Token.getMinBalanceRentForExemptAccount(connection()),

getMinimumBalanceForRentExemption: (dataLength: number) =>
connection().getMinimumBalanceForRentExemption(dataLength),

getEpochInfo: () => connection().getEpochInfo(),

config,
};
}
30 changes: 30 additions & 0 deletions src/families/solana/api/chain/instruction/system/index.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
import { ParsedInstruction } from "@solana/web3.js";
import { IX_STRUCTS, IX_TITLES, SystemInstructionType } from "./types";

import { ParsedInfo } from "../../validators";
import { create, Infer } from "superstruct";
import { PARSED_PROGRAMS } from "../../program/constants";

export function parseSystemInstruction(
ix: ParsedInstruction & { program: typeof PARSED_PROGRAMS.SYSTEM }
): SystemInstructionDescriptor {
const parsed = create(ix.parsed, ParsedInfo);
const { type: rawType, info } = parsed;
const type = create(rawType, SystemInstructionType);
const title = IX_TITLES[type];
const struct = IX_STRUCTS[type];

return {
type,
title: title as any,
info: create(info, struct as any) as any,
};
}

export type SystemInstructionDescriptor = {
[K in SystemInstructionType]: {
title: typeof IX_TITLES[K];
type: K;
info: Infer<typeof IX_STRUCTS[K]>;
};
}[SystemInstructionType];
Loading

1 comment on commit d70cf09

@github-actions
Copy link

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

($0.00) for Bot 'Solana on Mooncake'

⚠️ 8 mutations uncovered

Details of the 0 mutations

Spec Solana (failed)

Spec Solana found 1 Solana accounts (preload: 11.2s). Will use Solana 1.2.0 on nanoS 2.1.0
(18s) Solana 1 cross: 0 SOL (0ops) (5uHkqrUutcawYaaDh32z8dkoyZZpbLaXUPw3CGvueJGP on 44'/501'/0') solanaSub#0 js:2:solana:5uHkqrUutcawYaaDh32z8dkoyZZpbLaXUPw3CGvueJGP:solanaSub

This SEED does not have Solana. Please send funds to 5uHkqrUutcawYaaDh32z8dkoyZZpbLaXUPw3CGvueJGP

Details of the 8 uncovered mutations

Spec Solana (8)

  • Transfer ~50%:
  • Transfer Max:
  • Delegate:
  • Deactivate Activating Delegation:
  • Deactivate Active Delegation:
  • Reactivate Deactivating Delegation:
  • Activate Inactive Delegation:
  • Withdraw Delegation:

Portfolio ($0.00)

⚠️ 1 specs don't have enough funds! (Solana)

Details of the 1 currencies
Spec (accounts) Operations Balance funds?
Solana (0) 0 0 SOL ($0.00) ??? 5uHkqrUutcawYaaDh32z8dkoyZZpbLaXUPw3CGvueJGP

Please sign in to comment.