diff --git a/clients/js/jito_tip_router/accounts/ballotBox.ts b/clients/js/jito_tip_router/accounts/ballotBox.ts new file mode 100644 index 0000000..5c2ec9d --- /dev/null +++ b/clients/js/jito_tip_router/accounts/ballotBox.ts @@ -0,0 +1,163 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + assertAccountExists, + assertAccountsExist, + combineCodec, + decodeAccount, + fetchEncodedAccount, + fetchEncodedAccounts, + getAddressDecoder, + getAddressEncoder, + getArrayDecoder, + getArrayEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + getU8Decoder, + getU8Encoder, + type Account, + type Address, + type Codec, + type Decoder, + type EncodedAccount, + type Encoder, + type FetchAccountConfig, + type FetchAccountsConfig, + type MaybeAccount, + type MaybeEncodedAccount, +} from '@solana/web3.js'; +import { + getBallotTallyDecoder, + getBallotTallyEncoder, + getOperatorVoteDecoder, + getOperatorVoteEncoder, + type BallotTally, + type BallotTallyArgs, + type OperatorVote, + type OperatorVoteArgs, +} from '../types'; + +export type BallotBox = { + discriminator: bigint; + ncn: Address; + ncnEpoch: bigint; + bump: number; + slotCreated: bigint; + slotConsensusReached: bigint; + reserved: Array; + operatorsVoted: bigint; + uniqueBallots: bigint; + operatorVotes: Array; + ballotTallies: Array; +}; + +export type BallotBoxArgs = { + discriminator: number | bigint; + ncn: Address; + ncnEpoch: number | bigint; + bump: number; + slotCreated: number | bigint; + slotConsensusReached: number | bigint; + reserved: Array; + operatorsVoted: number | bigint; + uniqueBallots: number | bigint; + operatorVotes: Array; + ballotTallies: Array; +}; + +export function getBallotBoxEncoder(): Encoder { + return getStructEncoder([ + ['discriminator', getU64Encoder()], + ['ncn', getAddressEncoder()], + ['ncnEpoch', getU64Encoder()], + ['bump', getU8Encoder()], + ['slotCreated', getU64Encoder()], + ['slotConsensusReached', getU64Encoder()], + ['reserved', getArrayEncoder(getU8Encoder(), { size: 128 })], + ['operatorsVoted', getU64Encoder()], + ['uniqueBallots', getU64Encoder()], + ['operatorVotes', getArrayEncoder(getOperatorVoteEncoder(), { size: 32 })], + ['ballotTallies', getArrayEncoder(getBallotTallyEncoder(), { size: 32 })], + ]); +} + +export function getBallotBoxDecoder(): Decoder { + return getStructDecoder([ + ['discriminator', getU64Decoder()], + ['ncn', getAddressDecoder()], + ['ncnEpoch', getU64Decoder()], + ['bump', getU8Decoder()], + ['slotCreated', getU64Decoder()], + ['slotConsensusReached', getU64Decoder()], + ['reserved', getArrayDecoder(getU8Decoder(), { size: 128 })], + ['operatorsVoted', getU64Decoder()], + ['uniqueBallots', getU64Decoder()], + ['operatorVotes', getArrayDecoder(getOperatorVoteDecoder(), { size: 32 })], + ['ballotTallies', getArrayDecoder(getBallotTallyDecoder(), { size: 32 })], + ]); +} + +export function getBallotBoxCodec(): Codec { + return combineCodec(getBallotBoxEncoder(), getBallotBoxDecoder()); +} + +export function decodeBallotBox( + encodedAccount: EncodedAccount +): Account; +export function decodeBallotBox( + encodedAccount: MaybeEncodedAccount +): MaybeAccount; +export function decodeBallotBox( + encodedAccount: EncodedAccount | MaybeEncodedAccount +): Account | MaybeAccount { + return decodeAccount( + encodedAccount as MaybeEncodedAccount, + getBallotBoxDecoder() + ); +} + +export async function fetchBallotBox( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchMaybeBallotBox(rpc, address, config); + assertAccountExists(maybeAccount); + return maybeAccount; +} + +export async function fetchMaybeBallotBox( + rpc: Parameters[0], + address: Address, + config?: FetchAccountConfig +): Promise> { + const maybeAccount = await fetchEncodedAccount(rpc, address, config); + return decodeBallotBox(maybeAccount); +} + +export async function fetchAllBallotBox( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchAllMaybeBallotBox(rpc, addresses, config); + assertAccountsExist(maybeAccounts); + return maybeAccounts; +} + +export async function fetchAllMaybeBallotBox( + rpc: Parameters[0], + addresses: Array
, + config?: FetchAccountsConfig +): Promise[]> { + const maybeAccounts = await fetchEncodedAccounts(rpc, addresses, config); + return maybeAccounts.map((maybeAccount) => decodeBallotBox(maybeAccount)); +} diff --git a/clients/js/jito_tip_router/accounts/index.ts b/clients/js/jito_tip_router/accounts/index.ts index 549d9e8..9e306af 100644 --- a/clients/js/jito_tip_router/accounts/index.ts +++ b/clients/js/jito_tip_router/accounts/index.ts @@ -6,6 +6,7 @@ * @see https://github.com/kinobi-so/kinobi */ +export * from './ballotBox'; export * from './epochSnapshot'; export * from './ncnConfig'; export * from './operatorSnapshot'; diff --git a/clients/js/jito_tip_router/errors/jitoTipRouter.ts b/clients/js/jito_tip_router/errors/jitoTipRouter.ts index 45acb0f..ea08902 100644 --- a/clients/js/jito_tip_router/errors/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/errors/jitoTipRouter.ts @@ -74,9 +74,16 @@ export const JITO_TIP_ROUTER_ERROR__OPERATOR_FINALIZED = 0x2216; // 8726 export const JITO_TIP_ROUTER_ERROR__TOO_MANY_VAULT_OPERATOR_DELEGATIONS = 0x2217; // 8727 /** DuplicateVaultOperatorDelegation: Duplicate vault operator delegation */ export const JITO_TIP_ROUTER_ERROR__DUPLICATE_VAULT_OPERATOR_DELEGATION = 0x2218; // 8728 +/** DuplicateVoteCast: Duplicate Vote Cast */ +export const JITO_TIP_ROUTER_ERROR__DUPLICATE_VOTE_CAST = 0x2219; // 8729 +/** OperatorVotesFull: Operator votes full */ +export const JITO_TIP_ROUTER_ERROR__OPERATOR_VOTES_FULL = 0x221a; // 8730 +/** BallotTallyFull: Merkle root tally full */ +export const JITO_TIP_ROUTER_ERROR__BALLOT_TALLY_FULL = 0x221b; // 8731 export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__ARITHMETIC_OVERFLOW + | typeof JITO_TIP_ROUTER_ERROR__BALLOT_TALLY_FULL | typeof JITO_TIP_ROUTER_ERROR__CANNOT_CREATE_FUTURE_WEIGHT_TABLES | typeof JITO_TIP_ROUTER_ERROR__CAST_TO_IMPRECISE_NUMBER_ERROR | typeof JITO_TIP_ROUTER_ERROR__CONFIG_MINT_LIST_FULL @@ -84,6 +91,7 @@ export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__DENOMINATOR_IS_ZERO | typeof JITO_TIP_ROUTER_ERROR__DUPLICATE_MINTS_IN_TABLE | typeof JITO_TIP_ROUTER_ERROR__DUPLICATE_VAULT_OPERATOR_DELEGATION + | typeof JITO_TIP_ROUTER_ERROR__DUPLICATE_VOTE_CAST | typeof JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN | typeof JITO_TIP_ROUTER_ERROR__INCORRECT_NCN @@ -95,6 +103,7 @@ export type JitoTipRouterError = | typeof JITO_TIP_ROUTER_ERROR__NO_MINTS_IN_TABLE | typeof JITO_TIP_ROUTER_ERROR__NO_OPERATORS | typeof JITO_TIP_ROUTER_ERROR__OPERATOR_FINALIZED + | typeof JITO_TIP_ROUTER_ERROR__OPERATOR_VOTES_FULL | typeof JITO_TIP_ROUTER_ERROR__TOO_MANY_MINTS_FOR_TABLE | typeof JITO_TIP_ROUTER_ERROR__TOO_MANY_VAULT_OPERATOR_DELEGATIONS | typeof JITO_TIP_ROUTER_ERROR__TRACKED_MINT_LIST_FULL @@ -111,6 +120,7 @@ let jitoTipRouterErrorMessages: Record | undefined; if (process.env.NODE_ENV !== 'production') { jitoTipRouterErrorMessages = { [JITO_TIP_ROUTER_ERROR__ARITHMETIC_OVERFLOW]: `Overflow`, + [JITO_TIP_ROUTER_ERROR__BALLOT_TALLY_FULL]: `Merkle root tally full`, [JITO_TIP_ROUTER_ERROR__CANNOT_CREATE_FUTURE_WEIGHT_TABLES]: `Cannnot create future weight tables`, [JITO_TIP_ROUTER_ERROR__CAST_TO_IMPRECISE_NUMBER_ERROR]: `Cast to imprecise number error`, [JITO_TIP_ROUTER_ERROR__CONFIG_MINT_LIST_FULL]: `NCN config vaults are at capacity`, @@ -118,6 +128,7 @@ if (process.env.NODE_ENV !== 'production') { [JITO_TIP_ROUTER_ERROR__DENOMINATOR_IS_ZERO]: `Zero in the denominator`, [JITO_TIP_ROUTER_ERROR__DUPLICATE_MINTS_IN_TABLE]: `Duplicate mints in table`, [JITO_TIP_ROUTER_ERROR__DUPLICATE_VAULT_OPERATOR_DELEGATION]: `Duplicate vault operator delegation`, + [JITO_TIP_ROUTER_ERROR__DUPLICATE_VOTE_CAST]: `Duplicate Vote Cast`, [JITO_TIP_ROUTER_ERROR__FEE_CAP_EXCEEDED]: `Fee cap exceeded`, [JITO_TIP_ROUTER_ERROR__INCORRECT_FEE_ADMIN]: `Incorrect fee admin`, [JITO_TIP_ROUTER_ERROR__INCORRECT_NCN]: `Incorrect NCN`, @@ -129,6 +140,7 @@ if (process.env.NODE_ENV !== 'production') { [JITO_TIP_ROUTER_ERROR__NO_MINTS_IN_TABLE]: `There are no mints in the table`, [JITO_TIP_ROUTER_ERROR__NO_OPERATORS]: `No operators in ncn`, [JITO_TIP_ROUTER_ERROR__OPERATOR_FINALIZED]: `Operator is already finalized - should not happen`, + [JITO_TIP_ROUTER_ERROR__OPERATOR_VOTES_FULL]: `Operator votes full`, [JITO_TIP_ROUTER_ERROR__TOO_MANY_MINTS_FOR_TABLE]: `Too many mints for table`, [JITO_TIP_ROUTER_ERROR__TOO_MANY_VAULT_OPERATOR_DELEGATIONS]: `Too many vault operator delegations`, [JITO_TIP_ROUTER_ERROR__TRACKED_MINT_LIST_FULL]: `Tracked mints are at capacity`, diff --git a/clients/js/jito_tip_router/programs/jitoTipRouter.ts b/clients/js/jito_tip_router/programs/jitoTipRouter.ts index 29a98a9..3c57061 100644 --- a/clients/js/jito_tip_router/programs/jitoTipRouter.ts +++ b/clients/js/jito_tip_router/programs/jitoTipRouter.ts @@ -29,6 +29,7 @@ export const JITO_TIP_ROUTER_PROGRAM_ADDRESS = 'Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH' as Address<'Fv9aHCgvPQSr4jg9W8eTS6Ys1SNmh2qjyATrbsjEMaSH'>; export enum JitoTipRouterAccount { + BallotBox, EpochSnapshot, OperatorSnapshot, NcnConfig, diff --git a/clients/js/jito_tip_router/types/ballot.ts b/clients/js/jito_tip_router/types/ballot.ts new file mode 100644 index 0000000..c2ee14f --- /dev/null +++ b/clients/js/jito_tip_router/types/ballot.ts @@ -0,0 +1,59 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU64Decoder, + getU64Encoder, + type Codec, + type Decoder, + type Encoder, + type ReadonlyUint8Array, +} from '@solana/web3.js'; + +export type Ballot = { + merkleRoot: ReadonlyUint8Array; + maxTotalClaim: bigint; + maxNodeCount: bigint; + reserved: ReadonlyUint8Array; +}; + +export type BallotArgs = { + merkleRoot: ReadonlyUint8Array; + maxTotalClaim: number | bigint; + maxNodeCount: number | bigint; + reserved: ReadonlyUint8Array; +}; + +export function getBallotEncoder(): Encoder { + return getStructEncoder([ + ['merkleRoot', fixEncoderSize(getBytesEncoder(), 32)], + ['maxTotalClaim', getU64Encoder()], + ['maxNodeCount', getU64Encoder()], + ['reserved', fixEncoderSize(getBytesEncoder(), 64)], + ]); +} + +export function getBallotDecoder(): Decoder { + return getStructDecoder([ + ['merkleRoot', fixDecoderSize(getBytesDecoder(), 32)], + ['maxTotalClaim', getU64Decoder()], + ['maxNodeCount', getU64Decoder()], + ['reserved', fixDecoderSize(getBytesDecoder(), 64)], + ]); +} + +export function getBallotCodec(): Codec { + return combineCodec(getBallotEncoder(), getBallotDecoder()); +} diff --git a/clients/js/jito_tip_router/types/ballotTally.ts b/clients/js/jito_tip_router/types/ballotTally.ts new file mode 100644 index 0000000..99e151e --- /dev/null +++ b/clients/js/jito_tip_router/types/ballotTally.ts @@ -0,0 +1,67 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + fixDecoderSize, + fixEncoderSize, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU128Decoder, + getU128Encoder, + getU64Decoder, + getU64Encoder, + type Codec, + type Decoder, + type Encoder, + type ReadonlyUint8Array, +} from '@solana/web3.js'; +import { + getBallotDecoder, + getBallotEncoder, + type Ballot, + type BallotArgs, +} from '.'; + +export type BallotTally = { + ballot: Ballot; + stakeWeight: bigint; + tally: bigint; + reserved: ReadonlyUint8Array; +}; + +export type BallotTallyArgs = { + ballot: BallotArgs; + stakeWeight: number | bigint; + tally: number | bigint; + reserved: ReadonlyUint8Array; +}; + +export function getBallotTallyEncoder(): Encoder { + return getStructEncoder([ + ['ballot', getBallotEncoder()], + ['stakeWeight', getU128Encoder()], + ['tally', getU64Encoder()], + ['reserved', fixEncoderSize(getBytesEncoder(), 64)], + ]); +} + +export function getBallotTallyDecoder(): Decoder { + return getStructDecoder([ + ['ballot', getBallotDecoder()], + ['stakeWeight', getU128Decoder()], + ['tally', getU64Decoder()], + ['reserved', fixDecoderSize(getBytesDecoder(), 64)], + ]); +} + +export function getBallotTallyCodec(): Codec { + return combineCodec(getBallotTallyEncoder(), getBallotTallyDecoder()); +} diff --git a/clients/js/jito_tip_router/types/index.ts b/clients/js/jito_tip_router/types/index.ts index e93016f..b49207d 100644 --- a/clients/js/jito_tip_router/types/index.ts +++ b/clients/js/jito_tip_router/types/index.ts @@ -6,9 +6,12 @@ * @see https://github.com/kinobi-so/kinobi */ +export * from './ballot'; +export * from './ballotTally'; export * from './configAdminRole'; export * from './fee'; export * from './fees'; export * from './mintEntry'; +export * from './operatorVote'; export * from './vaultOperatorStakeWeight'; export * from './weightEntry'; diff --git a/clients/js/jito_tip_router/types/operatorVote.ts b/clients/js/jito_tip_router/types/operatorVote.ts new file mode 100644 index 0000000..a082c6a --- /dev/null +++ b/clients/js/jito_tip_router/types/operatorVote.ts @@ -0,0 +1,74 @@ +/** + * This code was AUTOGENERATED using the kinobi library. + * Please DO NOT EDIT THIS FILE, instead use visitors + * to add features, then rerun kinobi to update it. + * + * @see https://github.com/kinobi-so/kinobi + */ + +import { + combineCodec, + fixDecoderSize, + fixEncoderSize, + getAddressDecoder, + getAddressEncoder, + getBytesDecoder, + getBytesEncoder, + getStructDecoder, + getStructEncoder, + getU128Decoder, + getU128Encoder, + getU64Decoder, + getU64Encoder, + type Address, + type Codec, + type Decoder, + type Encoder, + type ReadonlyUint8Array, +} from '@solana/web3.js'; +import { + getBallotDecoder, + getBallotEncoder, + type Ballot, + type BallotArgs, +} from '.'; + +export type OperatorVote = { + operator: Address; + slotVoted: bigint; + stakeWeight: bigint; + ballot: Ballot; + reserved: ReadonlyUint8Array; +}; + +export type OperatorVoteArgs = { + operator: Address; + slotVoted: number | bigint; + stakeWeight: number | bigint; + ballot: BallotArgs; + reserved: ReadonlyUint8Array; +}; + +export function getOperatorVoteEncoder(): Encoder { + return getStructEncoder([ + ['operator', getAddressEncoder()], + ['slotVoted', getU64Encoder()], + ['stakeWeight', getU128Encoder()], + ['ballot', getBallotEncoder()], + ['reserved', fixEncoderSize(getBytesEncoder(), 64)], + ]); +} + +export function getOperatorVoteDecoder(): Decoder { + return getStructDecoder([ + ['operator', getAddressDecoder()], + ['slotVoted', getU64Decoder()], + ['stakeWeight', getU128Decoder()], + ['ballot', getBallotDecoder()], + ['reserved', fixDecoderSize(getBytesDecoder(), 64)], + ]); +} + +export function getOperatorVoteCodec(): Codec { + return combineCodec(getOperatorVoteEncoder(), getOperatorVoteDecoder()); +} diff --git a/clients/rust/jito_tip_router/src/generated/accounts/ballot_box.rs b/clients/rust/jito_tip_router/src/generated/accounts/ballot_box.rs new file mode 100644 index 0000000..640dc29 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/accounts/ballot_box.rs @@ -0,0 +1,75 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::generated::types::{BallotTally, OperatorVote}; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BallotBox { + pub discriminator: u64, + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub ncn: Pubkey, + pub ncn_epoch: u64, + pub bump: u8, + pub slot_created: u64, + pub slot_consensus_reached: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 128], + pub operators_voted: u64, + pub unique_ballots: u64, + pub operator_votes: [OperatorVote; 32], + pub ballot_tallies: [BallotTally; 32], +} + +impl BallotBox { + #[inline(always)] + pub fn from_bytes(data: &[u8]) -> Result { + let mut data = data; + Self::deserialize(&mut data) + } +} + +impl<'a> TryFrom<&solana_program::account_info::AccountInfo<'a>> for BallotBox { + type Error = std::io::Error; + + fn try_from( + account_info: &solana_program::account_info::AccountInfo<'a>, + ) -> Result { + let mut data: &[u8] = &(*account_info.data).borrow(); + Self::deserialize(&mut data) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountDeserialize for BallotBox { + fn try_deserialize_unchecked(buf: &mut &[u8]) -> anchor_lang::Result { + Ok(Self::deserialize(buf)?) + } +} + +#[cfg(feature = "anchor")] +impl anchor_lang::AccountSerialize for BallotBox {} + +#[cfg(feature = "anchor")] +impl anchor_lang::Owner for BallotBox { + fn owner() -> Pubkey { + crate::JITO_TIP_ROUTER_ID + } +} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::IdlBuild for BallotBox {} + +#[cfg(feature = "anchor-idl-build")] +impl anchor_lang::Discriminator for BallotBox { + const DISCRIMINATOR: [u8; 8] = [0; 8]; +} diff --git a/clients/rust/jito_tip_router/src/generated/accounts/mod.rs b/clients/rust/jito_tip_router/src/generated/accounts/mod.rs index 7b099a2..aa0f253 100644 --- a/clients/rust/jito_tip_router/src/generated/accounts/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/accounts/mod.rs @@ -4,6 +4,7 @@ //! //! +pub(crate) mod r#ballot_box; pub(crate) mod r#epoch_snapshot; pub(crate) mod r#ncn_config; pub(crate) mod r#operator_snapshot; @@ -11,6 +12,6 @@ pub(crate) mod r#tracked_mints; pub(crate) mod r#weight_table; pub use self::{ - r#epoch_snapshot::*, r#ncn_config::*, r#operator_snapshot::*, r#tracked_mints::*, - r#weight_table::*, + r#ballot_box::*, r#epoch_snapshot::*, r#ncn_config::*, r#operator_snapshot::*, + r#tracked_mints::*, r#weight_table::*, }; diff --git a/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs b/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs index 60dd7d6..491e234 100644 --- a/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs +++ b/clients/rust/jito_tip_router/src/generated/errors/jito_tip_router.rs @@ -99,6 +99,15 @@ pub enum JitoTipRouterError { /// 8728 - Duplicate vault operator delegation #[error("Duplicate vault operator delegation")] DuplicateVaultOperatorDelegation = 0x2218, + /// 8729 - Duplicate Vote Cast + #[error("Duplicate Vote Cast")] + DuplicateVoteCast = 0x2219, + /// 8730 - Operator votes full + #[error("Operator votes full")] + OperatorVotesFull = 0x221A, + /// 8731 - Merkle root tally full + #[error("Merkle root tally full")] + BallotTallyFull = 0x221B, } impl solana_program::program_error::PrintProgramError for JitoTipRouterError { diff --git a/clients/rust/jito_tip_router/src/generated/types/ballot.rs b/clients/rust/jito_tip_router/src/generated/types/ballot.rs new file mode 100644 index 0000000..1cd5b53 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/ballot.rs @@ -0,0 +1,17 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct Ballot { + pub merkle_root: [u8; 32], + pub max_total_claim: u64, + pub max_node_count: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 64], +} diff --git a/clients/rust/jito_tip_router/src/generated/types/ballot_tally.rs b/clients/rust/jito_tip_router/src/generated/types/ballot_tally.rs new file mode 100644 index 0000000..265dcf1 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/ballot_tally.rs @@ -0,0 +1,19 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; + +use crate::generated::types::Ballot; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct BallotTally { + pub ballot: Ballot, + pub stake_weight: u128, + pub tally: u64, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 64], +} diff --git a/clients/rust/jito_tip_router/src/generated/types/mod.rs b/clients/rust/jito_tip_router/src/generated/types/mod.rs index f19946a..82ebe6f 100644 --- a/clients/rust/jito_tip_router/src/generated/types/mod.rs +++ b/clients/rust/jito_tip_router/src/generated/types/mod.rs @@ -4,14 +4,17 @@ //! //! +pub(crate) mod r#ballot; +pub(crate) mod r#ballot_tally; pub(crate) mod r#config_admin_role; pub(crate) mod r#fee; pub(crate) mod r#fees; pub(crate) mod r#mint_entry; +pub(crate) mod r#operator_vote; pub(crate) mod r#vault_operator_stake_weight; pub(crate) mod r#weight_entry; pub use self::{ - r#config_admin_role::*, r#fee::*, r#fees::*, r#mint_entry::*, r#vault_operator_stake_weight::*, - r#weight_entry::*, + r#ballot::*, r#ballot_tally::*, r#config_admin_role::*, r#fee::*, r#fees::*, r#mint_entry::*, + r#operator_vote::*, r#vault_operator_stake_weight::*, r#weight_entry::*, }; diff --git a/clients/rust/jito_tip_router/src/generated/types/operator_vote.rs b/clients/rust/jito_tip_router/src/generated/types/operator_vote.rs new file mode 100644 index 0000000..f35d8d6 --- /dev/null +++ b/clients/rust/jito_tip_router/src/generated/types/operator_vote.rs @@ -0,0 +1,25 @@ +//! This code was AUTOGENERATED using the kinobi library. +//! Please DO NOT EDIT THIS FILE, instead use visitors +//! to add features, then rerun kinobi to update it. +//! +//! + +use borsh::{BorshDeserialize, BorshSerialize}; +use solana_program::pubkey::Pubkey; + +use crate::generated::types::Ballot; + +#[derive(BorshSerialize, BorshDeserialize, Clone, Debug, Eq, PartialEq)] +#[cfg_attr(feature = "serde", derive(serde::Serialize, serde::Deserialize))] +pub struct OperatorVote { + #[cfg_attr( + feature = "serde", + serde(with = "serde_with::As::") + )] + pub operator: Pubkey, + pub slot_voted: u64, + pub stake_weight: u128, + pub ballot: Ballot, + #[cfg_attr(feature = "serde", serde(with = "serde_with::As::"))] + pub reserved: [u8; 64], +} diff --git a/core/src/ballot_box.rs b/core/src/ballot_box.rs index 2c33031..f449bb2 100644 --- a/core/src/ballot_box.rs +++ b/core/src/ballot_box.rs @@ -7,11 +7,7 @@ use shank::{ShankAccount, ShankType}; use solana_program::{account_info::AccountInfo, msg, program_error::ProgramError, pubkey::Pubkey}; use spl_math::precise_number::PreciseNumber; -use crate::{ - constants::{MAX_OPERATORS, PRECISE_CONSENSUS}, - discriminators::Discriminators, - error::TipRouterError, -}; +use crate::{constants::PRECISE_CONSENSUS, discriminators::Discriminators, error::TipRouterError}; #[derive(Debug, Clone, PartialEq, Eq, Copy, Zeroable, ShankType, Pod, ShankType)] #[repr(C)] @@ -43,7 +39,7 @@ impl Ballot { } } - pub fn root(&self) -> [u8; 32] { + pub const fn root(&self) -> [u8; 32] { self.merkle_root } @@ -86,7 +82,7 @@ impl BallotTally { } } - pub fn ballot(&self) -> Ballot { + pub const fn ballot(&self) -> Ballot { self.ballot } @@ -151,7 +147,7 @@ impl OperatorVote { } } - pub fn operator(&self) -> Pubkey { + pub const fn operator(&self) -> Pubkey { self.operator } @@ -163,7 +159,7 @@ impl OperatorVote { self.stake_weight.into() } - pub fn ballot(&self) -> Ballot { + pub const fn ballot(&self) -> Ballot { self.ballot } @@ -190,8 +186,9 @@ pub struct BallotBox { operators_voted: PodU64, unique_ballots: PodU64, - operator_votes: [OperatorVote; 256], - ballot_tallies: [BallotTally; 256], + //TODO fix 32 -> MAX_OPERATORS + operator_votes: [OperatorVote; 32], + ballot_tallies: [BallotTally; 32], } impl Discriminator for BallotBox { @@ -208,8 +205,9 @@ impl BallotBox { slot_consensus_reached: PodU64::from(0), operators_voted: PodU64::from(0), unique_ballots: PodU64::from(0), - operator_votes: [OperatorVote::default(); MAX_OPERATORS], - ballot_tallies: [BallotTally::default(); MAX_OPERATORS], + //TODO fix 32 -> MAX_OPERATORS + operator_votes: [OperatorVote::default(); 32], + ballot_tallies: [BallotTally::default(); 32], reserved: [0; 128], } } @@ -305,7 +303,7 @@ impl BallotBox { } } - Err(TipRouterError::BallotTallyFull.into()) + Err(TipRouterError::BallotTallyFull) } pub fn cast_vote( @@ -317,7 +315,7 @@ impl BallotBox { ) -> Result<(), TipRouterError> { for vote in self.operator_votes.iter_mut() { if vote.operator().eq(&operator) { - return Err(TipRouterError::DuplicateVoteCast.into()); + return Err(TipRouterError::DuplicateVoteCast); } if vote.is_empty() { @@ -336,7 +334,7 @@ impl BallotBox { } } - Err(TipRouterError::OperatorVotesFull.into()) + Err(TipRouterError::OperatorVotesFull) } //Not sure where/how this should be used @@ -345,6 +343,10 @@ impl BallotBox { total_stake_weight: u128, current_slot: u64, ) -> Result<(), TipRouterError> { + if self.slot_consensus_reached() != 0 { + return Err(TipRouterError::ConsensusAlreadyReached); + } + let max_tally = self .ballot_tallies .iter() @@ -367,7 +369,7 @@ impl BallotBox { let consensus_reached = ballot_percentage_of_total.greater_than_or_equal(&target_precise_percentage); - if consensus_reached && self.slot_consensus_reached() != 0 { + if consensus_reached { self.slot_consensus_reached = PodU64::from(current_slot); } diff --git a/core/src/error.rs b/core/src/error.rs index 74e7990..b133ff1 100644 --- a/core/src/error.rs +++ b/core/src/error.rs @@ -70,6 +70,8 @@ pub enum TipRouterError { OperatorVotesFull, #[error("Merkle root tally full")] BallotTallyFull, + #[error("Consensus already reached")] + ConsensusAlreadyReached, } impl DecodeError for TipRouterError { diff --git a/idl/jito_tip_router.json b/idl/jito_tip_router.json index 7f59e84..1f9c803 100644 --- a/idl/jito_tip_router.json +++ b/idl/jito_tip_router.json @@ -566,6 +566,83 @@ } ], "accounts": [ + { + "name": "BallotBox", + "type": { + "kind": "struct", + "fields": [ + { + "name": "ncn", + "type": "publicKey" + }, + { + "name": "ncnEpoch", + "type": { + "defined": "PodU64" + } + }, + { + "name": "bump", + "type": "u8" + }, + { + "name": "slotCreated", + "type": { + "defined": "PodU64" + } + }, + { + "name": "slotConsensusReached", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 128 + ] + } + }, + { + "name": "operatorsVoted", + "type": { + "defined": "PodU64" + } + }, + { + "name": "uniqueBallots", + "type": { + "defined": "PodU64" + } + }, + { + "name": "operatorVotes", + "type": { + "array": [ + { + "defined": "OperatorVote" + }, + 32 + ] + } + }, + { + "name": "ballotTallies", + "type": { + "array": [ + { + "defined": "BallotTally" + }, + 32 + ] + } + } + ] + } + }, { "name": "EpochSnapshot", "type": { @@ -870,6 +947,118 @@ } ], "types": [ + { + "name": "Ballot", + "type": { + "kind": "struct", + "fields": [ + { + "name": "merkleRoot", + "type": { + "array": [ + "u8", + 32 + ] + } + }, + { + "name": "maxTotalClaim", + "type": { + "defined": "PodU64" + } + }, + { + "name": "maxNodeCount", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } + } + ] + } + }, + { + "name": "BallotTally", + "type": { + "kind": "struct", + "fields": [ + { + "name": "ballot", + "type": { + "defined": "Ballot" + } + }, + { + "name": "stakeWeight", + "type": { + "defined": "PodU128" + } + }, + { + "name": "tally", + "type": { + "defined": "PodU64" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } + } + ] + } + }, + { + "name": "OperatorVote", + "type": { + "kind": "struct", + "fields": [ + { + "name": "operator", + "type": "publicKey" + }, + { + "name": "slotVoted", + "type": { + "defined": "PodU64" + } + }, + { + "name": "stakeWeight", + "type": { + "defined": "PodU128" + } + }, + { + "name": "ballot", + "type": { + "defined": "Ballot" + } + }, + { + "name": "reserved", + "type": { + "array": [ + "u8", + 64 + ] + } + } + ] + } + }, { "name": "VaultOperatorStakeWeight", "type": { @@ -1190,6 +1379,21 @@ "code": 8728, "name": "DuplicateVaultOperatorDelegation", "msg": "Duplicate vault operator delegation" + }, + { + "code": 8729, + "name": "DuplicateVoteCast", + "msg": "Duplicate Vote Cast" + }, + { + "code": 8730, + "name": "OperatorVotesFull", + "msg": "Operator votes full" + }, + { + "code": 8731, + "name": "BallotTallyFull", + "msg": "Merkle root tally full" } ], "metadata": {