Skip to content

Commit

Permalink
AUDIT: C-06 Voting can be done after routing started, leading to a Do…
Browse files Browse the repository at this point in the history
…S for the router (#49)

now checking if voting is closed before distribution
  • Loading branch information
coachchucksol authored Jan 16, 2025
1 parent 485b038 commit e41732d
Show file tree
Hide file tree
Showing 12 changed files with 137 additions and 27 deletions.
3 changes: 3 additions & 0 deletions cli/src/instructions.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1060,6 +1060,8 @@ pub async fn route_base_rewards(handler: &CliHandler, epoch: u64) -> Result<()>
let (epoch_state, _, _) =
EpochState::find_program_address(&handler.tip_router_program_id, &ncn, epoch);

let config = TipRouterConfig::find_program_address(&jito_tip_router_program::id(), &ncn).0;

let (epoch_snapshot, _, _) =
EpochSnapshot::find_program_address(&handler.tip_router_program_id, &ncn, epoch);

Expand All @@ -1079,6 +1081,7 @@ pub async fn route_base_rewards(handler: &CliHandler, epoch: u64) -> Result<()>
while still_routing {
let route_base_rewards_ix = RouteBaseRewardsBuilder::new()
.epoch_state(epoch_state)
.config(config)
.ncn(ncn)
.epoch_snapshot(epoch_snapshot)
.ballot_box(ballot_box)
Expand Down
4 changes: 4 additions & 0 deletions clients/js/jito_tip_router/errors/jitoTipRouter.ts
Original file line number Diff line number Diff line change
Expand Up @@ -170,6 +170,8 @@ export const JITO_TIP_ROUTER_ERROR__INVALID_ACCOUNT_STATUS = 0x2242; // 8770
export const JITO_TIP_ROUTER_ERROR__ACCOUNT_ALREADY_INITIALIZED = 0x2243; // 8771
/** BadBallot: Cannot vote with uninitialized account */
export const JITO_TIP_ROUTER_ERROR__BAD_BALLOT = 0x2244; // 8772
/** VotingIsNotOver: Cannot route until voting is over */
export const JITO_TIP_ROUTER_ERROR__VOTING_IS_NOT_OVER = 0x2245; // 8773

export type JitoTipRouterError =
| typeof JITO_TIP_ROUTER_ERROR__ACCOUNT_ALREADY_INITIALIZED
Expand Down Expand Up @@ -243,6 +245,7 @@ export type JitoTipRouterError =
| typeof JITO_TIP_ROUTER_ERROR__VAULT_REGISTRY_LIST_FULL
| typeof JITO_TIP_ROUTER_ERROR__VAULT_REGISTRY_VAULT_LOCKED
| typeof JITO_TIP_ROUTER_ERROR__VAULT_REWARD_NOT_FOUND
| typeof JITO_TIP_ROUTER_ERROR__VOTING_IS_NOT_OVER
| typeof JITO_TIP_ROUTER_ERROR__VOTING_NOT_FINALIZED
| typeof JITO_TIP_ROUTER_ERROR__VOTING_NOT_VALID
| typeof JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_LENGTH
Expand Down Expand Up @@ -325,6 +328,7 @@ if (process.env.NODE_ENV !== 'production') {
[JITO_TIP_ROUTER_ERROR__VAULT_REGISTRY_LIST_FULL]: `Vault Registry mints are at capacity`,
[JITO_TIP_ROUTER_ERROR__VAULT_REGISTRY_VAULT_LOCKED]: `Vault registry are locked for the epoch`,
[JITO_TIP_ROUTER_ERROR__VAULT_REWARD_NOT_FOUND]: `Vault Reward not found`,
[JITO_TIP_ROUTER_ERROR__VOTING_IS_NOT_OVER]: `Cannot route until voting is over`,
[JITO_TIP_ROUTER_ERROR__VOTING_NOT_FINALIZED]: `Voting not finalized`,
[JITO_TIP_ROUTER_ERROR__VOTING_NOT_VALID]: `Voting not valid, too many slots after consensus reached`,
[JITO_TIP_ROUTER_ERROR__WEIGHT_MINTS_DO_NOT_MATCH_LENGTH]: `Weight mints do not match - length`,
Expand Down
26 changes: 20 additions & 6 deletions clients/js/jito_tip_router/instructions/routeBaseRewards.ts
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@ export function getRouteBaseRewardsDiscriminatorBytes() {
export type RouteBaseRewardsInstruction<
TProgram extends string = typeof JITO_TIP_ROUTER_PROGRAM_ADDRESS,
TAccountEpochState extends string | IAccountMeta<string> = string,
TAccountConfig extends string | IAccountMeta<string> = string,
TAccountNcn extends string | IAccountMeta<string> = string,
TAccountEpochSnapshot extends string | IAccountMeta<string> = string,
TAccountBallotBox extends string | IAccountMeta<string> = string,
Expand All @@ -53,6 +54,9 @@ export type RouteBaseRewardsInstruction<
TAccountEpochState extends string
? WritableAccount<TAccountEpochState>
: TAccountEpochState,
TAccountConfig extends string
? ReadonlyAccount<TAccountConfig>
: TAccountConfig,
TAccountNcn extends string ? ReadonlyAccount<TAccountNcn> : TAccountNcn,
TAccountEpochSnapshot extends string
? ReadonlyAccount<TAccountEpochSnapshot>
Expand Down Expand Up @@ -112,13 +116,15 @@ export function getRouteBaseRewardsInstructionDataCodec(): Codec<

export type RouteBaseRewardsInput<
TAccountEpochState extends string = string,
TAccountConfig extends string = string,
TAccountNcn extends string = string,
TAccountEpochSnapshot extends string = string,
TAccountBallotBox extends string = string,
TAccountBaseRewardRouter extends string = string,
TAccountBaseRewardReceiver extends string = string,
> = {
epochState: Address<TAccountEpochState>;
config: Address<TAccountConfig>;
ncn: Address<TAccountNcn>;
epochSnapshot: Address<TAccountEpochSnapshot>;
ballotBox: Address<TAccountBallotBox>;
Expand All @@ -130,6 +136,7 @@ export type RouteBaseRewardsInput<

export function getRouteBaseRewardsInstruction<
TAccountEpochState extends string,
TAccountConfig extends string,
TAccountNcn extends string,
TAccountEpochSnapshot extends string,
TAccountBallotBox extends string,
Expand All @@ -139,6 +146,7 @@ export function getRouteBaseRewardsInstruction<
>(
input: RouteBaseRewardsInput<
TAccountEpochState,
TAccountConfig,
TAccountNcn,
TAccountEpochSnapshot,
TAccountBallotBox,
Expand All @@ -149,6 +157,7 @@ export function getRouteBaseRewardsInstruction<
): RouteBaseRewardsInstruction<
TProgramAddress,
TAccountEpochState,
TAccountConfig,
TAccountNcn,
TAccountEpochSnapshot,
TAccountBallotBox,
Expand All @@ -162,6 +171,7 @@ export function getRouteBaseRewardsInstruction<
// Original accounts.
const originalAccounts = {
epochState: { value: input.epochState ?? null, isWritable: true },
config: { value: input.config ?? null, isWritable: false },
ncn: { value: input.ncn ?? null, isWritable: false },
epochSnapshot: { value: input.epochSnapshot ?? null, isWritable: false },
ballotBox: { value: input.ballotBox ?? null, isWritable: false },
Expand All @@ -186,6 +196,7 @@ export function getRouteBaseRewardsInstruction<
const instruction = {
accounts: [
getAccountMeta(accounts.epochState),
getAccountMeta(accounts.config),
getAccountMeta(accounts.ncn),
getAccountMeta(accounts.epochSnapshot),
getAccountMeta(accounts.ballotBox),
Expand All @@ -199,6 +210,7 @@ export function getRouteBaseRewardsInstruction<
} as RouteBaseRewardsInstruction<
TProgramAddress,
TAccountEpochState,
TAccountConfig,
TAccountNcn,
TAccountEpochSnapshot,
TAccountBallotBox,
Expand All @@ -216,11 +228,12 @@ export type ParsedRouteBaseRewardsInstruction<
programAddress: Address<TProgram>;
accounts: {
epochState: TAccountMetas[0];
ncn: TAccountMetas[1];
epochSnapshot: TAccountMetas[2];
ballotBox: TAccountMetas[3];
baseRewardRouter: TAccountMetas[4];
baseRewardReceiver: TAccountMetas[5];
config: TAccountMetas[1];
ncn: TAccountMetas[2];
epochSnapshot: TAccountMetas[3];
ballotBox: TAccountMetas[4];
baseRewardRouter: TAccountMetas[5];
baseRewardReceiver: TAccountMetas[6];
};
data: RouteBaseRewardsInstructionData;
};
Expand All @@ -233,7 +246,7 @@ export function parseRouteBaseRewardsInstruction<
IInstructionWithAccounts<TAccountMetas> &
IInstructionWithData<Uint8Array>
): ParsedRouteBaseRewardsInstruction<TProgram, TAccountMetas> {
if (instruction.accounts.length < 6) {
if (instruction.accounts.length < 7) {
// TODO: Coded error.
throw new Error('Not enough accounts');
}
Expand All @@ -247,6 +260,7 @@ export function parseRouteBaseRewardsInstruction<
programAddress: instruction.programAddress,
accounts: {
epochState: getNextAccount(),
config: getNextAccount(),
ncn: getNextAccount(),
epochSnapshot: getNextAccount(),
ballotBox: getNextAccount(),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -244,6 +244,9 @@ pub enum JitoTipRouterError {
/// 8772 - Cannot vote with uninitialized account
#[error("Cannot vote with uninitialized account")]
BadBallot = 0x2244,
/// 8773 - Cannot route until voting is over
#[error("Cannot route until voting is over")]
VotingIsNotOver = 0x2245,
}

impl solana_program::program_error::PrintProgramError for JitoTipRouterError {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,8 @@ use borsh::BorshSerialize;
pub struct RouteBaseRewards {
pub epoch_state: solana_program::pubkey::Pubkey,

pub config: solana_program::pubkey::Pubkey,

pub ncn: solana_program::pubkey::Pubkey,

pub epoch_snapshot: solana_program::pubkey::Pubkey,
Expand All @@ -36,11 +38,15 @@ impl RouteBaseRewards {
args: RouteBaseRewardsInstructionArgs,
remaining_accounts: &[solana_program::instruction::AccountMeta],
) -> solana_program::instruction::Instruction {
let mut accounts = Vec::with_capacity(6 + remaining_accounts.len());
let mut accounts = Vec::with_capacity(7 + remaining_accounts.len());
accounts.push(solana_program::instruction::AccountMeta::new(
self.epoch_state,
false,
));
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
self.config,
false,
));
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
self.ncn, false,
));
Expand Down Expand Up @@ -102,14 +108,16 @@ pub struct RouteBaseRewardsInstructionArgs {
/// ### Accounts:
///
/// 0. `[writable]` epoch_state
/// 1. `[]` ncn
/// 2. `[]` epoch_snapshot
/// 3. `[]` ballot_box
/// 4. `[writable]` base_reward_router
/// 5. `[writable]` base_reward_receiver
/// 1. `[]` config
/// 2. `[]` ncn
/// 3. `[]` epoch_snapshot
/// 4. `[]` ballot_box
/// 5. `[writable]` base_reward_router
/// 6. `[writable]` base_reward_receiver
#[derive(Clone, Debug, Default)]
pub struct RouteBaseRewardsBuilder {
epoch_state: Option<solana_program::pubkey::Pubkey>,
config: Option<solana_program::pubkey::Pubkey>,
ncn: Option<solana_program::pubkey::Pubkey>,
epoch_snapshot: Option<solana_program::pubkey::Pubkey>,
ballot_box: Option<solana_program::pubkey::Pubkey>,
Expand All @@ -130,6 +138,11 @@ impl RouteBaseRewardsBuilder {
self
}
#[inline(always)]
pub fn config(&mut self, config: solana_program::pubkey::Pubkey) -> &mut Self {
self.config = Some(config);
self
}
#[inline(always)]
pub fn ncn(&mut self, ncn: solana_program::pubkey::Pubkey) -> &mut Self {
self.ncn = Some(ncn);
self
Expand Down Expand Up @@ -192,6 +205,7 @@ impl RouteBaseRewardsBuilder {
pub fn instruction(&self) -> solana_program::instruction::Instruction {
let accounts = RouteBaseRewards {
epoch_state: self.epoch_state.expect("epoch_state is not set"),
config: self.config.expect("config is not set"),
ncn: self.ncn.expect("ncn is not set"),
epoch_snapshot: self.epoch_snapshot.expect("epoch_snapshot is not set"),
ballot_box: self.ballot_box.expect("ballot_box is not set"),
Expand All @@ -218,6 +232,8 @@ impl RouteBaseRewardsBuilder {
pub struct RouteBaseRewardsCpiAccounts<'a, 'b> {
pub epoch_state: &'b solana_program::account_info::AccountInfo<'a>,

pub config: &'b solana_program::account_info::AccountInfo<'a>,

pub ncn: &'b solana_program::account_info::AccountInfo<'a>,

pub epoch_snapshot: &'b solana_program::account_info::AccountInfo<'a>,
Expand All @@ -236,6 +252,8 @@ pub struct RouteBaseRewardsCpi<'a, 'b> {

pub epoch_state: &'b solana_program::account_info::AccountInfo<'a>,

pub config: &'b solana_program::account_info::AccountInfo<'a>,

pub ncn: &'b solana_program::account_info::AccountInfo<'a>,

pub epoch_snapshot: &'b solana_program::account_info::AccountInfo<'a>,
Expand All @@ -258,6 +276,7 @@ impl<'a, 'b> RouteBaseRewardsCpi<'a, 'b> {
Self {
__program: program,
epoch_state: accounts.epoch_state,
config: accounts.config,
ncn: accounts.ncn,
epoch_snapshot: accounts.epoch_snapshot,
ballot_box: accounts.ballot_box,
Expand Down Expand Up @@ -299,11 +318,15 @@ impl<'a, 'b> RouteBaseRewardsCpi<'a, 'b> {
bool,
)],
) -> solana_program::entrypoint::ProgramResult {
let mut accounts = Vec::with_capacity(6 + remaining_accounts.len());
let mut accounts = Vec::with_capacity(7 + remaining_accounts.len());
accounts.push(solana_program::instruction::AccountMeta::new(
*self.epoch_state.key,
false,
));
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
*self.config.key,
false,
));
accounts.push(solana_program::instruction::AccountMeta::new_readonly(
*self.ncn.key,
false,
Expand Down Expand Up @@ -340,9 +363,10 @@ impl<'a, 'b> RouteBaseRewardsCpi<'a, 'b> {
accounts,
data,
};
let mut account_infos = Vec::with_capacity(6 + 1 + remaining_accounts.len());
let mut account_infos = Vec::with_capacity(7 + 1 + remaining_accounts.len());
account_infos.push(self.__program.clone());
account_infos.push(self.epoch_state.clone());
account_infos.push(self.config.clone());
account_infos.push(self.ncn.clone());
account_infos.push(self.epoch_snapshot.clone());
account_infos.push(self.ballot_box.clone());
Expand All @@ -365,11 +389,12 @@ impl<'a, 'b> RouteBaseRewardsCpi<'a, 'b> {
/// ### Accounts:
///
/// 0. `[writable]` epoch_state
/// 1. `[]` ncn
/// 2. `[]` epoch_snapshot
/// 3. `[]` ballot_box
/// 4. `[writable]` base_reward_router
/// 5. `[writable]` base_reward_receiver
/// 1. `[]` config
/// 2. `[]` ncn
/// 3. `[]` epoch_snapshot
/// 4. `[]` ballot_box
/// 5. `[writable]` base_reward_router
/// 6. `[writable]` base_reward_receiver
#[derive(Clone, Debug)]
pub struct RouteBaseRewardsCpiBuilder<'a, 'b> {
instruction: Box<RouteBaseRewardsCpiBuilderInstruction<'a, 'b>>,
Expand All @@ -380,6 +405,7 @@ impl<'a, 'b> RouteBaseRewardsCpiBuilder<'a, 'b> {
let instruction = Box::new(RouteBaseRewardsCpiBuilderInstruction {
__program: program,
epoch_state: None,
config: None,
ncn: None,
epoch_snapshot: None,
ballot_box: None,
Expand All @@ -400,6 +426,14 @@ impl<'a, 'b> RouteBaseRewardsCpiBuilder<'a, 'b> {
self
}
#[inline(always)]
pub fn config(
&mut self,
config: &'b solana_program::account_info::AccountInfo<'a>,
) -> &mut Self {
self.instruction.config = Some(config);
self
}
#[inline(always)]
pub fn ncn(&mut self, ncn: &'b solana_program::account_info::AccountInfo<'a>) -> &mut Self {
self.instruction.ncn = Some(ncn);
self
Expand Down Expand Up @@ -503,6 +537,8 @@ impl<'a, 'b> RouteBaseRewardsCpiBuilder<'a, 'b> {
.epoch_state
.expect("epoch_state is not set"),

config: self.instruction.config.expect("config is not set"),

ncn: self.instruction.ncn.expect("ncn is not set"),

epoch_snapshot: self
Expand Down Expand Up @@ -534,6 +570,7 @@ impl<'a, 'b> RouteBaseRewardsCpiBuilder<'a, 'b> {
struct RouteBaseRewardsCpiBuilderInstruction<'a, 'b> {
__program: &'b solana_program::account_info::AccountInfo<'a>,
epoch_state: Option<&'b solana_program::account_info::AccountInfo<'a>>,
config: Option<&'b solana_program::account_info::AccountInfo<'a>>,
ncn: Option<&'b solana_program::account_info::AccountInfo<'a>>,
epoch_snapshot: Option<&'b solana_program::account_info::AccountInfo<'a>>,
ballot_box: Option<&'b solana_program::account_info::AccountInfo<'a>>,
Expand Down
2 changes: 2 additions & 0 deletions core/src/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -161,6 +161,8 @@ pub enum TipRouterError {
AccountAlreadyInitialized,
#[error("Cannot vote with uninitialized account")]
BadBallot,
#[error("Cannot route until voting is over")]
VotingIsNotOver,
}

impl<T> DecodeError<T> for TipRouterError {
Expand Down
11 changes: 6 additions & 5 deletions core/src/instruction.rs
Original file line number Diff line number Diff line change
Expand Up @@ -263,11 +263,12 @@ pub enum TipRouterInstruction {

/// Routes base reward router
#[account(0, writable, name = "epoch_state")]
#[account(1, name = "ncn")]
#[account(2, name = "epoch_snapshot")]
#[account(3, name = "ballot_box")]
#[account(4, writable, name = "base_reward_router")]
#[account(5, writable, name = "base_reward_receiver")]
#[account(1, name = "config")]
#[account(2, name = "ncn")]
#[account(3, name = "epoch_snapshot")]
#[account(4, name = "ballot_box")]
#[account(5, writable, name = "base_reward_router")]
#[account(6, writable, name = "base_reward_receiver")]
RouteBaseRewards{
max_iterations: u16,
epoch: u64,
Expand Down
Loading

0 comments on commit e41732d

Please sign in to comment.