Halborn Logo

icon

Gorples Chef & Farm updates - Entangle Labs


Prepared by:

Halborn Logo

HALBORN

Last Updated 07/30/2024

Date of Engagement by: July 11th, 2024 - July 15th, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

4

Critical

0

High

0

Medium

0

Low

3

Informational

1


1. Introduction

Entangle Labs team engaged Halborn to conduct a security assessment on a series of changes in their Gorples (ex Borpa) programs, SOL Chef Solana Program and SOL Farm Solana Program, beginning on July 7th, 2024 and ending on July 15th, 2024. The security assessment was scoped to the smart contracts provided in the GitHub repository gorples-solana, commit hashes, and further details can be found in the Scope section of this report.

The recent updates have involved modifications to the add_liquidity, remove_liquidity, swap, and harvest instructions with the following objectives:

    • Implementing continuous rewards distribution

    • Resolving operational issues related to Raydium integration on the mainnet

2. Assessment Summary

Halborn was provided 3 days for the engagement and assigned two full-time security engineers to review the security of the Solana Programs in scope. The engineers are blockchain and smart contract security experts with advanced smart contract hacking skills, and deep knowledge of multiple blockchain protocols.

The purpose of the assessment is to:

    • Identify potential security issues within the Solana Programs.

    • Ensure that smart contract functionality operates as intended.

 

In summary, Halborn identified some security concerns, that were acknowledged or had its risk accepted by the Entangle Labs team. The main ones were the following:

    • Balance Check Missing When Easy Mode in Add Liquidity

    • Insufficient verification of Refund Amounts in Add Liquidity

    • Users Rewards Loss if Farm Set Start Rewards After Deposits.


Note: As of the date of this report's finalization, no meaningful documentation of program performance and changes, as well as a functional test suite to perform the security tests locally, was provided. The extensive number of accounts and dependencies required to deploy the local environment for security testing, combined with the complexity of developing such an environment under these circumstances, exceeded the scope of this security assessment.

As a result, we were limited to conducting a comprehensive code review of the relevant changes. This limitation prevents us from guaranteeing 100% code security.

3. Test Approach and Methodology

Halborn performed a combination of a manual review of the source code and automated security testing to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of the program assessment. While manual testing is recommended to uncover flaws in business logic, processes, and implementation; automated testing techniques help enhance coverage of programs and can quickly identify items that do not follow security best practices.

The following phases and associated tools were used throughout the term of the assessment:

    • Research into the architecture, purpose, and use of the platform.

    • Manual program source code review to identify business logic issues.

    • Mapping out possible attack vectors

    • Thorough assessment of safety and usage of critical Rust variables and functions in scope that could lead to arithmetic vulnerabilities.

    • Scanning dependencies for known vulnerabilities (cargo audit).

4. RISK METHODOLOGY

Every vulnerability and issue observed by Halborn is ranked based on two sets of Metrics and a Severity Coefficient. This system is inspired by the industry standard Common Vulnerability Scoring System.
The two Metric sets are: Exploitability and Impact. Exploitability captures the ease and technical means by which vulnerabilities can be exploited and Impact describes the consequences of a successful exploit.
The Severity Coefficients is designed to further refine the accuracy of the ranking with two factors: Reversibility and Scope. These capture the impact of the vulnerability on the environment as well as the number of users and smart contracts affected.
The final score is a value between 0-10 rounded up to 1 decimal place and 10 corresponding to the highest security risk. This provides an objective and accurate rating of the severity of security vulnerabilities in smart contracts.
The system is designed to assist in identifying and prioritizing vulnerabilities based on their level of risk to address the most critical issues in a timely manner.

4.1 EXPLOITABILITY

Attack Origin (AO):
Captures whether the attack requires compromising a specific account.
Attack Cost (AC):
Captures the cost of exploiting the vulnerability incurred by the attacker relative to sending a single transaction on the relevant blockchain. Includes but is not limited to financial and computational cost.
Attack Complexity (AX):
Describes the conditions beyond the attacker’s control that must exist in order to exploit the vulnerability. Includes but is not limited to macro situation, available third-party liquidity and regulatory challenges.
Metrics:
EXPLOITABILIY METRIC (mem_e)METRIC VALUENUMERICAL VALUE
Attack Origin (AO)Arbitrary (AO:A)
Specific (AO:S)
1
0.2
Attack Cost (AC)Low (AC:L)
Medium (AC:M)
High (AC:H)
1
0.67
0.33
Attack Complexity (AX)Low (AX:L)
Medium (AX:M)
High (AX:H)
1
0.67
0.33
Exploitability EE is calculated using the following formula:

E=meE = \prod m_e

4.2 IMPACT

Confidentiality (C):
Measures the impact to the confidentiality of the information resources managed by the contract due to a successfully exploited vulnerability. Confidentiality refers to limiting access to authorized users only.
Integrity (I):
Measures the impact to integrity of a successfully exploited vulnerability. Integrity refers to the trustworthiness and veracity of data stored and/or processed on-chain. Integrity impact directly affecting Deposit or Yield records is excluded.
Availability (A):
Measures the impact to the availability of the impacted component resulting from a successfully exploited vulnerability. This metric refers to smart contract features and functionality, not state. Availability impact directly affecting Deposit or Yield is excluded.
Deposit (D):
Measures the impact to the deposits made to the contract by either users or owners.
Yield (Y):
Measures the impact to the yield generated by the contract for either users or owners.
Metrics:
IMPACT METRIC (mIm_I)METRIC VALUENUMERICAL VALUE
Confidentiality (C)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Integrity (I)None (I:N)
Low (I:L)
Medium (I:M)
High (I:H)
Critical (I:C)
0
0.25
0.5
0.75
1
Availability (A)None (A:N)
Low (A:L)
Medium (A:M)
High (A:H)
Critical (A:C)
0
0.25
0.5
0.75
1
Deposit (D)None (D:N)
Low (D:L)
Medium (D:M)
High (D:H)
Critical (D:C)
0
0.25
0.5
0.75
1
Yield (Y)None (Y:N)
Low (Y:L)
Medium (Y:M)
High (Y:H)
Critical (Y:C)
0
0.25
0.5
0.75
1
Impact II is calculated using the following formula:

I=max(mI)+mImax(mI)4I = max(m_I) + \frac{\sum{m_I} - max(m_I)}{4}

4.3 SEVERITY COEFFICIENT

Reversibility (R):
Describes the share of the exploited vulnerability effects that can be reversed. For upgradeable contracts, assume the contract private key is available.
Scope (S):
Captures whether a vulnerability in one vulnerable contract impacts resources in other contracts.
Metrics:
SEVERITY COEFFICIENT (CC)COEFFICIENT VALUENUMERICAL VALUE
Reversibility (rr)None (R:N)
Partial (R:P)
Full (R:F)
1
0.5
0.25
Scope (ss)Changed (S:C)
Unchanged (S:U)
1.25
1
Severity Coefficient CC is obtained by the following product:

C=rsC = rs

The Vulnerability Severity Score SS is obtained by:

S=min(10,EIC10)S = min(10, EIC * 10)

The score is rounded up to 1 decimal places.
SeverityScore Value Range
Critical9 - 10
High7 - 8.9
Medium4.5 - 6.9
Low2 - 4.4
Informational0 - 1.9

5. SCOPE

Files and Repository
(a) Repository: gorples-solana
(b) Assessed Commit ID: 47c4433
(c) Items in scope:
  • /programs/borpa-chef/src/instructions/add_liquidity.rs
  • /programs/borpa-chef/src/instructions/remove_liquidity.rs
  • /programs/borpa-chef/src/instructions/swap.rs
↓ Expand ↓
Out-of-Scope:
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

0

Low

3

Informational

1

Security analysisRisk levelRemediation Date
BALANCE CHECK MISSING WHEN EASY MODE IN ADD LIQUIDITYLowRisk Accepted
INSUFFICIENT VERIFICATION OF REFUND AMOUNTS IN ADD LIQUIDITYLowRisk Accepted
USERS REWARDS LOSS IF FARM SET START REWARDS AFTER DEPOSITSLowRisk Accepted
MULTIPLE TOKEN ACCOUNTS CHECK MISSINGInformationalAcknowledged

7. Findings & Tech Details

7.1 BALANCE CHECK MISSING WHEN EASY MODE IN ADD LIQUIDITY

// Low

Description

The AddLiquidity instruction allows the operation in easy mode by providing the is_easy_mode value as parameter to true. Additionally, the amount_0 and amount_1 values are provided. When easy mode is enabled, the liquidity addition values are derived from subtracting amount_0 and amount_1 from the amount of token_account_0 and token_account_1 accounts, respectively. However, this operation does not verify if the amount of token_account_0 and token_account_1 are greater than the provided amount_0 and amount_1 values. If not, this would result in a panic due to an overflow error.

add_liquidity.rs

pub fn handle_add_liquidity<'info>(
    ctx: Context<'_, '_, '_, 'info, AddLiquidity<'info>>,
    amount_0: u64,
    amount_1: u64,
    is_base_amount0: bool,
    is_easy_mode: bool,
    deposit_fee_rate: u64,
) -> Result<u128> {
    solana_program::log::sol_log("start");
    let (amount_0, amount_1) = if is_easy_mode {
        (
            ctx.accounts.token_source_0.amount - amount_0,
            ctx.accounts.token_source_1.amount - amount_1,
        )
    } else {
        (amount_0, amount_1)
    };

A similar issue arises when any user calls the Swap instruction, which swaps or transfers funds from token_account_0 and token_account_1 to the wsol_vault. If someone then attempts to add liquidity using easy mode, and the token_account_0 and token_account_1 accounts lack sufficient funds, it will cause the same panic due to an overflow error as described until token_account_0 and token_account_1 have enough funds.

swap.rs

#[derive(Accounts)]
pub struct Swap<'info> {
    #[account(signer)]
    pub user: Signer<'info>,

    #[account(seeds = [FARM_ROOT, b"CHEF_CONFIG"], bump)]
    pub chef_config: Box<Account<'info, ChefConfig>>,

    /// CHECK: authority account
    #[account(seeds = [FARM_ROOT, b"AUTHORITY"], bump)]
    pub authority: UncheckedAccount<'info>,

    #[account(
           seeds = [FARM_ROOT, b"POOL_CONFIG", token_mint_0.key().as_ref(), token_mint_1.key().as_ref()],
           bump
    )]
    pub pool_config: Box<Account<'info, PoolConfig>>,


BVSS
Recommendation

To address this issue, it is recommended to add a validation to check the amount of token_account_0 and token_account_1 is higher than the provided amount_0 and amount_1 values, if is_easy_mode.

Remediation Plan

RISK ACCEPTED: The Entangle team accepted the risk related to this finding.

7.2 INSUFFICIENT VERIFICATION OF REFUND AMOUNTS IN ADD LIQUIDITY

// Low

Description

The AddLiquidity instruction, called by deposit, calculates the corresponding amounts to increase liquidity based on the user-provided amount_0 and amount_1, considering the following:

  • Transfer fees that will be internally deducted during transfer_checked.

  • fee0 and fee1, based on the deposit_fee_rate of the pool, which will be charged for adding liquidity

Once calculated, the liquidity is increased accordingly. If there is an excess amount between what was transferred to token_account_0 and token_account_1 and the amount used to increase liquidity after deducting the mentioned fees, this excess is refunded to the user as refund0 and refund1.

If either of the calculated refund values (refund0 or refund1) is greater than zero but less than the transfer fee deducted during TransferChecked, it will panic due to an overflow error. This is due to a lack of verification in the instruction handler, as the transfer fee will be subtracted from the intended refund amount.


add_liquidity.rs

  ctx.accounts.token_account_0.reload()?;
    ctx.accounts.token_account_1.reload()?;
    let vault0_after_deposit = ctx.accounts.token_account_0.amount;
    let vault1_after_deposit = ctx.accounts.token_account_1.amount;
    let leftover0 = vault0_after_deposit - vault0_before_transfer;
    let leftover1 = vault1_after_deposit - vault1_before_transfer;
    solana_program::log::sol_log(&format!(
        "leftover0: {} leftover1: {}",
        leftover0, leftover1
    ));
    let refund0 = if leftover0 > fee0 {
        leftover0 - fee0
    } else {
        0
    };
    let refund1 = if leftover1 > fee1 {
        leftover1 - fee1
    } else {
        0
    };
    solana_program::log::sol_log(&format!("refund0: {} refund1: {}", refund0, refund1));
    if refund0 > 0 {
        // Transfer token 0
        let accounts = TransferChecked {
            from: ctx.accounts.token_account_0.to_account_info(),
            mint: ctx.accounts.token_mint_0.to_account_info(),
            to: ctx.accounts.token_source_0.to_account_info(),
            authority: ctx.accounts.authority.to_account_info(),
        };
        let cpi = CpiContext::new_with_signer(ctx.accounts.get_token_program_0(), accounts, seeds);
        transfer_checked(cpi, refund0, ctx.accounts.token_mint_0.decimals)?;
    }
    if refund1 > 0 {
        // Transfer token 1
        let accounts = TransferChecked {
            from: ctx.accounts.token_account_1.to_account_info(),
            mint: ctx.accounts.token_mint_1.to_account_info(),
            to: ctx.accounts.token_source_1.to_account_info(),
            authority: ctx.accounts.authority.to_account_info(),
        };
        let cpi = CpiContext::new_with_signer(ctx.accounts.get_token_program_1(), accounts, seeds);
        transfer_checked(cpi, refund1, ctx.accounts.token_mint_1.decimals)?;
    }
BVSS
Recommendation

To address this issue, it is recommended to add to the validation a check that verifies the amount to be transferred as refund is greater than zero and is also greater than the corresponding transfer fee, if it is any.

Remediation Plan

RISK ACCEPTED: The Entangle team accepted the risk related to this finding.

7.3 USERS REWARDS LOSS IF FARM SET START REWARDS AFTER DEPOSITS

// Low

Description

During the initialization of the FarmState, the start_time is assigned a default value of zero. If this value is not modified before users make deposits, it will create the impression that the FarmState has started, allowing deposit to be performed and rewards to be obtained.

harvest.rs

impl FarmState {
    pub const LEN: usize = 8 + 32 + 8 * 7;

    pub fn new(config: FarmConfig) -> Self {
        Self {
            admin: config.admin,
            xborpa_cooldown: config.xborpa_cooldown,
            xborpa_redeem_fee: config.xborpa_redeem_fee,
            xborpa_haircut: config.xborpa_haircut,
            total_borpa_minted: 0,
            total_xborpa: 0,
            allocated_reward_share: 0,
            start_time: 0,
        }
    }

When users make their first deposits, their new positions will be initialized through the Harvest instruction, updating their last_claim_timestamp to the current unix_timestamp. The Harvest instruction handles the necessary calculations for users to receive rewards based on the last_claim_timestamp of their position at the time of the call.

However, the SetStartRewards instruction allows the admin to modify the start_time at any moment.

set_start_rewards.rs

pub fn handle_set_start_rewards(ctx: Context<SetStartRewards>, start_time: u64) -> Result<()> {
    ctx.accounts
        .farm_state
        .set_start_rewards(start_time, ctx.accounts.clock.unix_timestamp as u64)?;
    Ok(())

state.rs

pub fn set_start_rewards(
        &mut self,
        start_time: u64,
        now: u64,
    ) -> std::result::Result<(), CustomError> {
        /*if self.start_time != 0 && now > self.start_time {
            return Err(CustomError::RewardsAlreadyStarted);
        }*/
        if now > start_time && start_time != 0 {
            return Err(CustomError::InvalidStartTime);
        }
        self.start_time = start_time;
        Ok(())

If this value is changed to postpone the start_time after deposits have been made, users who have previously deposited will lose the rewards corresponding to the days that have passed since they deposited.This occurs because rewards cannot be calculated until the Farm has started. Once the Farm begins, the last_claim_timestamp will be updated based on the time of the call.

harvest.rs

  if !self.farm_state.is_started(self.clock.unix_timestamp as u64) {
            return Ok(());
        }

        if self.position.last_claim_timestamp == 0
            || self.position.last_claim_timestamp < self.farm_state.start_time
        {
            self.position.last_claim_timestamp = self.clock.unix_timestamp as u64;
            return Ok(());
        }
BVSS
Recommendation

To address this issue, consider implementing one of the following options:

  • Disallow deposits if the Farm has is not started.

  • Prevent changes to the start time of the Farm State once deposits have been made.

Remediation Plan

RISK ACCEPTED: The Entangle team accepted the risk related to this finding.

7.4 MULTIPLE TOKEN ACCOUNTS CHECK MISSING

// Informational

Description

The Harvest instruction requires several accounts to be provided, the borpa_target_vault among them, where the amount of borpa rewards will be minted to. However, it is not validated that the authority of the account matches that of the signing user, allowing any borpa token account to be provided.

harvest.rs

    /// Target vault to claim $BORPA to
    #[account(mut)]
    pub borpa_target_vault: Box<InterfaceAccount<'info, TokenAccount>>,
let accounts = MintBorpa {
            mint_authority: self.authority.to_account_info(),
            authority: self.borpa_core_authority.to_account_info(),
            config: self.borpa_core_config.to_account_info(),
            mint: self.borpa_mint.to_account_info(),
            vault: self.borpa_target_vault.to_account_info(),
            token_program: self.token_program_2022.to_account_info(),
        };
        let bump = &[authority_bump][..];
        let seed = &[FARM_ROOT, b"AUTHORITY", bump][..];
        let seeds = &[seed];
        let cpi = CpiContext::new_with_signer(self.borpa_core.to_account_info(), accounts, seeds);
        self.farm_state.register_mint(borpa)?;
        mint_borpa(cpi, borpa)?;
BVSS
Recommendation

Consider adding the missing validation as a best practice to ensure that borpa rewards are minted in the user's borpa token account if expected.

Remediation Plan

ACKNOWLEDGED: The Entangle team has acknowledged this finding.

8. Automated Testing

Static Analysis Report
Description

Halborn used automated security scanners to assist with detection of well-known security issues and vulnerabilities. Among the tools used was cargo audit, a security scanner for vulnerabilities reported to the RustSec Advisory Database. All vulnerabilities published in https://crates.io are stored in a repository named The RustSec Advisory Database. cargo audit is a human-readable version of the advisory database which performs a scanning on Cargo.lock. Security Detections are only in scope. All vulnerabilities shown here were already disclosed in the above report. However, to better assist the developers maintaining this code, the auditors are including the output with the dependencies tree, and this is included in the cargo audit output to better know the dependencies affected by unmaintained and vulnerable crates.

Cargo Audit Results


ID

Crate

Desccription

RUSTSEC-2022-0093

ed25519-dalek

Double Public Key Signing Function Oracle Attack on ed255109-dalek

RUSTSEC-2024-0344

curve25519-dalek

Timing variability in curve25519-dalek's Scalar29::sub/`Scalar52::sub`

RUSTSEC-2024-0336

rustls

rustls::ConnectionCommon::complete_io could fall into an infinite loop based on network input

RUSTSEC-2023-0065

tungstenite

 Tungstenite allows remote attackers to cause a denial of service


Halborn strongly recommends conducting a follow-up assessment of the project either within six months or immediately following any material changes to the codebase, whichever comes first. This approach is crucial for maintaining the project’s integrity and addressing potential vulnerabilities introduced by code modifications.