Hawksight security record

As of 3 Nov 2026, Hawksight has 1 audit report on Trustblock, by CertiK, dated 28 Sep 2022. That report lists no open critical findings.

Critical findings
No open critical findings
In 1 report.
Open the latest report
Your label, live
Get the label for your site

Record last updated . It holds audits and findings, contracts and incidents up to Nov 2026. Trustblock does not review code.

Audits1

Newest first

AuditSecurity firmDateScopeCritical findingsReport
HawksightSecurity firm: CertiKDate: Scope: Not recorded (see the report)No open critical findingsOpen · skynet.certik.com

Findings30

Statuses are the security firm’s own, from each report.

SeverityOpenAcknowledgedFixed
Critical000
High202
Medium003
Low3020
HighLack of Authentication for `create_state`Hawksight · CertiK · 28 Sep 2022Fixed

The `create_state` function is designed to initialize a globally unique `state` account. This `state` account will be used to identify the project owner and to ensure that only the owner can create a `strategy` and set the `token_per_second` of the farm. ```rust pub struct CreateState<'info> { #[account( init, seeds = [b"state".as_ref()], bump, payer = authority, space = 8 + size_of::<StateAccount>() )] pub state: Account<'info, StateAccount>, ... } ``` ```rust pub fn create_state(&mut self, bump: u8) -> Result<()> { let state = &mut self.state; state.authority = self.authority.key(); state.bump = bump; state.reward_mint = self.reward_mint.key(); state.reward_vault = self.reward_vault.key(); emit!(StateCreated {}); Ok(()) } ``` Any given program_id can only have one `state` account. This may allow malicious users to pre-empt the creation of `state` by posting `create_state` transactions first, thus making the program unavailable. Additionally, if a malicious user manages to call `create_state` prior to admin of the program, the malicious attacker would then have a parallel running program who's state is controlled by them. The user could use this to impersonate the real program to exploit users.

Open the report
HighLack of TestsHawksight · CertiK · 28 Sep 2022Fixed

The unit tests here are not enough. Testing programs are a very important aspect of proving program correctness, preventing regressions, and release engineering. Without tests, there is no way to know if the program works as expected. The fact that code was shipped for review with such major flaw, is considered a signal of some problems with release engineering procedures, which may lead to shipping not intended changes to production.

Open the report
MediumLack of Length Check for `weights` and `remainingHawksight · CertiK · 28 Sep 2022Fixed

In the `farm` account, the `asset_infos` array is initialized by `weights` and `remaining_accounts`. - `mints` vector is initialized by iterating over `ctx.remaining_accounts`. ```rust for mint in _ctx.remaining_accounts.iter(){ require!(*mint.owner == token::ID, ErrorCode::InvalidMint); Mint::unpack(&mint.to_account_info().try_borrow_data()?)?; mints.push(mint.key()); } ``` - `asset_size` is defined by the length of `weights`. ```rust let asset_size = weights.len(); let asset_count = asset_size.try_into().unwrap(); ``` - `mints` is used to calculate the `farm`'s `seed`. ```rust for mint in mints.iter() { seeds.push(mint.as_ref()); } ``` - `farm.asset_infos` is filled by iterating over the elements of `mints` and `weights` up to `asset_size`. ```rust for i in 0 .. asset_size{ total_weight += u128::from(weights[i]); farm.asset_infos.push(FarmAssetInfo{ weight: weights[i], mint: mints[i] }) } ``` However, the length of `weights` and `remaining_accounts`/`mints` are not checked during this initialization process. This makes it possible that the farm's `seed` and its `asset_infos` to not match, making the service unusable.

Open the report
MediumMissing Validation for `FarmRewardInfo::update` aHawksight · CertiK · 28 Sep 2022Fixed

The function `change_token_per_second` updates the farms `token_per_second` parameter. The function provides no validation for when the function should be called. ```rust impl<'info> ChangeTokenPerSecondMulti<'info>{ pub fn change_token_per_second(&mut self, token_per_seconds: Vec<u128> ) -> Result<()> { let farm = &mut self.farm; let farm_key = farm.key(); farm.validate_address(farm_key)?; let asset_count:u8 = token_per_seconds.len().try_into().unwrap(); require!( asset_count == farm.asset_count, ErrorCode::InvalidAssetCount); let mut index:usize = 0; for token_per_second in token_per_seconds.iter() { farm.reward_infos[index].token_per_second = *token_per_second; index += 1; } Ok(()) } } ``` In the `state.rs`, the `update()` function will calculate the rewards that have been generated for a farm. ```rust pub fn update<'info>(&mut self, clock: &Sysvar<'info, Clock>) -> Result<()> { let seconds = u128::try_from( clock .unix_timestamp .checked_sub(self.last_reward_time) .unwrap(), ) .unwrap(); let mut reward_per_share: u128 = 0; if self.amount > 0 && seconds > 0 { reward_per_share = u128::from(self.token_per_second) .checked_mul(seconds) .unwrap() .checked_mul(ACC_PRECISION) .unwrap() .checked_div(u128::from(self.amount)) .unwrap(); } self.acc_reward_per_share = self .acc_reward_per_share .checked_add(reward_per_share) .unwrap(); self.last_reward_time = clock.unix_timestamp; Ok(()) } ``` Neither `update` nor `change_token_per_second` have any validation for _when_ `token_per_second` should be changed. In practice, this could mean someone calling `change_token_per_second` prior to when they are supposed to, which would result in unexpected results when calling `update`. The logic of when `token_per_second` should change is totally off-chain and in the hands of the owner, without any validation. Both human error and malicious intent are risks with the current architecture of this function.

Open the report
MediumTerra's UST Should not be Used as a Stable CoinHawksight · CertiK · 28 Sep 2022Fixed

Note that with the `UST` price now below $0.05, and no longer has the role of a stable coin. This may easily affect functionality of UST in the project. ```rust pub const UST_MAINNET: Pubkey = static_pubkey::static_pubkey!("9vMJfxuKxXBoEa7rM12mYLMwTacLMLDJqHozw96WQL8i"); pub const STABLE_MINTS: [Pubkey; 2] = [USDC_MAINNET, UST_MAINNET]; ```

Open the report
Low`last_amount` Not Reset to ZeroHawksight · CertiK · 28 Sep 2022Fixed

After withdrawing reward, the user's `last_action` is set as `FinishAction`, however the users `last_amount` is not reset to 0 after processing. For comparison, `stake_to_farm()` and `redeem_stable_token()`, after a `FinishAction` reset the `last_amount` to zero. This could cause undefined behavior in the future. For example ```rust=919 pub fn withdraw_reward(&mut self) -> Result<()> { ... for asset_index in 0..usize::from(farm.asset_count) { ... user_asset.calculate_reward_debt(&farm_reward)?; user_asset.last_action = Action::FinishAction as u8; user_asset.last_amount = 0; // reset as zero before FinishAction } ... } ```

Open the report
LowAdd non-zero Check for `total_weight`Hawksight · CertiK · 28 Sep 2022Fixed

`total_weight` will be used in the future to assign `amount` to different `asset_info.last_amount`. ```rust user.asset_infos[index].last_amount = u128::from(new_amount) .checked_mul(u128::from(asset_info.weight)) .unwrap() .checked_div(farm.total_weight) .unwrap() .try_into() .unwrap(); ``` But the instruction doesn't have a validation over the weights and the total weight. It is important to make sure that `total_weight` is not equal to 0 in order to prevent the construction of unavailable `farm` accounts. ```rust pub fn create_farm(&mut self, ... ) -> Result<()> { for i in 0 .. asset_size{ total_weight += u128::from(weights[i]); ... } farm.total_weight = total_weight; } ```

Open the report
LowIncorrect `bump` ImplementationHawksight · CertiK · 28 Sep 2022Fixed

By adding an empty `bump` constraint to the `#[account(...)]` macro, you signal Anchor to find the canonical bump on its own for the initialization of the account. The user defined `bump` will be written to storage, but will not be used for account initialization. ```rust = 21 pub fn create_state(&mut self, bump: u8, ) -> Result<()> { ... state.bump = bump; ... } ``` Anywhere that the storage `state.bump` is used will result in incorrect validation of account addresses. Reference material on bump creation can be found [here](https://docs.rs/anchor-lang/latest/anchor_lang/context/struct.Context.html#structfield.bumps) and [here](https://www.anchor-lang.com/docs/pdas)

Open the report
LowLack of `Asset` Length Limit CheckHawksight · CertiK · 28 Sep 2022Fixed

`MAX_ASSET_COUNT` is used to determine the total size that is to be allocated for accounts in `create_or_allocate_account_raw`. ```rust pub const MAX_ASSET_COUNT:usize = 10; ``` However, when this upper limit is not checked when allocating `asset_infos` in `farm`. This may result in the allocated space of the account being depleted. ```rust for i in 0..asset_size { total_weight += u128::from(weights[i]); farm.asset_infos.push(FarmAssetInfo { weight: weights[i], mint: mints[i], }) } ```

Open the report
LowLack of Mint Validation for User Token AccountsHawksight · CertiK · 28 Sep 2022Fixed

`Mint` is the only basis for identifying different types of tokens in Solana, and it is important to make sure that the mint in the a given Token Accounts data, which is input by the user, is correct. The current program only checks whether the address of the Token Account is constructed by the right seeds and program id, via the `check_token_account` function. It is best practice to include mint and ownership checks when interacting with the SPL Token program. For example, a validation that guarantees the Token Accounts mint address is the same as the one expected in the `strategy`. ```rust require!(token_account.mint == strategy.src_mint, ErrorCode::CustomErrorCode); ``` This would guarantee the token account was not created incorrectly.

Open the report
Show 10 more
LowMissing `Action` CheckHawksight · CertiK · 28 Sep 2022Fixed

According to the logic, **Hawksight** program forms a complete process in strict accordance with the `Action` steps. The project strictly controls the process execution through the inspection of `Action`. For example, in `supply_liquidity`, the function can only be run if the `last_action` was a `SwapAction` ```rust pub fn supply_liquidity(&mut self, min_amount_out: u64) -> Result<()> { ... require!( (user_asset_info.last_action == Action::SwapAction as u8) || ((user_asset_info.last_action == Action::FundAction as u8) && (strategy.token_mint == USDC_MAINNET) && ((strategy.src_mint == USDC_MAINNET) || (strategy.dst_mint == USDC_MAINNET))), ErrorCode::InvalidOperationOrder ); ... ``` However, `Action` is not verified in `stake_to_farm()` and `unstake_from_farm()` function, which may lead to unexpected errors in cross-step operations.

Open the report
LowOptimize `creator_fee` Calculation for Improved PHawksight · CertiK · 28 Sep 2022Fixed

The current computation of `creator_fee` does not conform to the multiply first, divide later specification. This will result in a loss of precision in the final value. ```rust let fee_amount = u128::from(input_amount) .checked_mul(fee_pct) .unwrap() .checked_div(FEE_DENOMINATOR) .unwrap(); let creator_fee = fee_amount .checked_mul(CREATOR_FEE_WEIGHT) .unwrap() .checked_div(...).unwrap() .unwrap(); ```

Open the report
LowReduce the Use of `std:mem:size_of()`Hawksight · CertiK · 28 Sep 2022Fixed

[`mem::size_of<T>()`](https://doc.rust-lang.org/std/mem/fn.size_of.html) should be used for size calculations with caution. Borsch will always serialize an option as 1 byte for the variant identifier and then additional x bytes for the content if it's Some. However, Rust uses null-pointer optimization to make Option's variant identifier 0 bytes when it can, so an option is sometimes just as big as its contents, such as with `Sign`.

Open the report
LowRemoval of Unnecessary Checks for Computing BudgeHawksight · CertiK · 28 Sep 2022Fixed

There are some unnecessary checks in the current code. For example: - **Already checked by `check_sub`** ```rust = 341 user.asset_infos[index].last_amount = u128::from(new_amount) .checked_mul(u128::from(asset_info.weight)) .unwrap() .checked_div(farm.total_weight) .unwrap() .try_into() .unwrap(); user.asset_infos[index].last_action = Action::FundAction as u8; require!( rest_amount >= user.asset_infos[index].last_amount, ErrorCode::IntegerUnderflow );//Duplicate check rest_amount = rest_amount .checked_sub(user.asset_infos[index].last_amount) .unwrap(); index += 1; ``` - **Already checked by `#[account(mut,seeds = [..], bump)]`** ```rust check_token_account( self.user_pda_stable_token.key(), farm.stable_mint, farm_key, user_key, )?; ``` - **Already checked by `Program<'info, Token>`** ```rust #[account(constraint = token_program.key == &token::ID)] ```

Open the report
LowSimplifiable `require` OperationHawksight · CertiK · 28 Sep 2022Fixed

In `require!` macro, checking the `x == true` expression is redundant. ```rust require!(found == true, ErrorCode::AssetNotFound); ```

Open the report
LowSimplified Implementation of `index` in LoopHawksight · CertiK · 28 Sep 2022Fixed

The code in the following loop uses `index`, but it can be further simplified. ```rust=341 for asset_info in farm.asset_infos.iter(){ user.asset_infos[index].last_amount = u128::from(new_amount) .checked_mul(u128::from(asset_info.weight)).unwrap() .checked_div(farm.total_weight).unwrap() .try_into().unwrap(); user.asset_infos[index].last_action = Action::FundAction as u8; require!(rest_amount >= user.asset_infos[index].last_amount, ErrorCode::IntegerUnderflow); rest_amount = rest_amount.checked_sub(user.asset_infos[index].last_amount).unwrap(); index += 1; } ``` ```rust = 170 for token_per_second in token_per_seconds.iter() { farm.reward_infos[index].token_per_second = *token_per_second; index += 1; } ```

Open the report
LowStaked and Unstaked Amounts Can Become InconsisteHawksight · CertiK · 28 Sep 2022Fixed

In the `stake_to_farm()` function, the user can stake the LP obtained by adding liquidity to `saber_farm` to obtain rewards. ```rust stake_lp_token_pda( self.saber_farm_program.to_account_info(), self.token_program.to_account_info(), user.to_account_info(), &self.saber_farm, self.user_pda_lp_token.to_account_info(), self.saber_farm_rewarder.to_account_info(), // Stake full LP token balance of pass through account self.user_pda_lp_token.amount, // user.asset_infos[asset_index].last_amount, authority_seeds, )?; ``` However, the stake amount is `user_pda_lp_token.amount`, but the billed amount is `user_asset.last_amount`. ```rust user_asset.amount = user_asset .amount .checked_add(user_asset.last_amount) .unwrap(); ``` If there is an external transaction that transfers the additional LPs to the `user_pda_lp_token`, this will cause the actual stake amount to be inconsistent with the billed amount, users cannot get back all staked LPs when unstaking from the farm.

Open the report
LowTypoHawksight · CertiK · 28 Sep 2022Fixed

The comment includes a spelling mistake of `further`.

Open the report
LowUnnecessary `&` ReferenceHawksight · CertiK · 28 Sep 2022Fixed

The references on the linked lines would be dereferenced immediately by the compiler, so the borrow operations are unnecessary.

Open the report
LowUnnecessary `return` StatementHawksight · CertiK · 28 Sep 2022Fixed

There is an unnecessary `return` statement in the current code base. ```rust = 620 if exp_token_address != address { return Err(Error::ProgramError(ProgramErrorWithOrigin::from(ProgramError::InvalidArgument))); } ```

Open the report
Show 5 more
LowUnnecessary AccountHawksight · CertiK · 28 Sep 2022Fixed

The `CreateUserToken` struct declares a `strategy` account, however the only field used in that account is the token mint. Passing around unnecessary data can increase complexity of the program, as well as computational requirements. ```rust pub struct CreateUserToken<'info> { #[account( seeds = [ b"saber-farm-strategy".as_ref(), strategy.token_mint.as_ref(), strategy.src_mint.as_ref(), strategy.dst_mint.as_ref(), strategy.lp_mint.as_ref(), strategy.spl_swap.as_ref(), strategy.saber_swap.as_ref(), strategy.quarry.as_ref(), ], bump, )] pub strategy: Account<'info, SaberFarmStrategy>, // ... } ``` ```rust pub fn create_user_token(&mut self) -> Result<()> { let farm = &mut self.farm; let strategy = &mut self.strategy; let farm_key = farm.key(); farm.validate_address(farm_key)?; farm.find_asset(strategy.token_mint)?; // Used here Ok(()) } ```

Open the report
LowUnnecessary Conversion to the Same TypeHawksight · CertiK · 28 Sep 2022Fixed

The references on the linked lines make unnecessary conversions to the same type.

Open the report
LowUnnecessary Re-slicingHawksight · CertiK · 28 Sep 2022Fixed

In the `utils.rs`, `authority_seeds` was re-sliced, which was unnecessary: ```rust 393 with_signer(&[&authority_seeds[..]]) ```

Open the report
LowUnused VariableHawksight · CertiK · 28 Sep 2022Fixed

The variable `_res` is declared but is not used in the code logic. ```rust=550 let _res = redeem_all_tokens_from_mint_proxy(CpiContext::new(...)); ```

Open the report
LowUse `create_miner_v2 ` Instead of `create_miner`Hawksight · CertiK · 28 Sep 2022Fixed

The QuarryProtocol provides [`create_miner`](https://github.com/QuarryProtocol/quarry/blob/f41219dd9e60fbabe5494a6dffbbc17dc3778a46/programs/quarry-mine/src/lib.rs#L232) to initialize the miner. However, since the current `create_miner` still requires `bump` for initialization, QuarryProtocol provides a more standardized [`create_miner_v2`](https://github.com/QuarryProtocol/quarry/blob/f41219dd9e60fbabe5494a6dffbbc17dc3778a46/programs/quarry-mine/src/lib.rs#L242) to replace `create_miner`. ```rust create_miner(CpiContext::new( self.quarry_mine_program.to_account_info(), CreateMiner{ authority: self.user.to_account_info(), miner: self.miner.to_account_info(), quarry: self.quarry.to_account_info(), rewarder: self.rewarder.to_account_info(), system_program: self.system_program.to_account_info(), payer: self.authority.to_account_info(), token_mint: self.lp_mint.to_account_info(), miner_vault: self.miner_vault.to_account_info(), token_program: self.token_program.to_account_info(), }).with_signer(&[&authority_seeds[..]]), miner_bump)?; ```

Open the report

Contracts

No deployed address is recorded for these audits.

The reports name a repository or files. Open a report to see its scope.

Show this on your site

Is this your project? Show this record on your own website. The label states what the record holds, links to this page and updates by itself. It is free and needs no sign-in.

Content

Facts

Automatic: the three strongest facts of this project’s own record (its last audit and two more), highlighted. They update with the record. Picking a fact turns Automatic off.

Fixed as
Language

Style

Layout
Theme
Accent
Corners

Format

Size
Date
Embed as
HTML
<head>
<script src="https://widget.trustblock.run/project-widget.min.js" defer></script>
</head>
<body>
<div
data-tb-project-widget
data-tb-project-slug="hawksight"
></div>
</body>

Paste the code where the label should appear. It uses no API credit and updates by itself.

Something wrong on this page? A missing report, a wrong date, a finding that was fixed since. Tell us and we check it against its source.

Tell us