Genius Solana Program V2 - Shuttle Labs


Prepared by:

Halborn Logo

HALBORN

Last Updated 12/17/2024

Date of Engagement: November 19th, 2024 - November 28th, 2024

Summary

100% of all REPORTED Findings have been addressed

All findings

4

Critical

0

High

0

Medium

0

Low

2

Informational

2


1. Introduction

Shuttle labs engaged Halborn to conduct a security assessment on their Solana Validator program beginning on November 19th, 2024 and ending on November 28th, 2024. The security assessment was scoped to the smart contracts provided in the GitHub repository genius-contracts-solana, commit hashes, and further details can be found in the Scope section of this report.


The Shuttle labs a new version of the Genius program. This new version uses orchestrators to manage most of the needed functions to operate in the Genius ecosystem.

2. Assessment Summary

Halborn was provided 8 days for the engagement and assigned one full-time security engineer to review the security of the Solana Programs in scope. The engineer is a blockchain and smart contract security expert 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 codebase.

    • Validate that the user's funds are safe

    • Verify that the logic implemented by Shuttle Labs does not introduce any vulnerability

In summary, Halborn identified some improvements to reduce the likelihood and impact of multiple risks, which were partially addressed by the Shuttle Labs team. The main ones were the following:

    • Implement the conf parameter to calculate the USDC value according to Pyth documentation.

    • Add a validation in create_order to check that both source and destination chains are different.

3. Test Approach and Methodology

Halborn performed a combination of manual review and security testing based on scripts to balance efficiency, timeliness, practicality, and accuracy in regard to the scope of this assessment. While manual testing is recommended to uncover flaws in logic, process, and implementation; automated testing techniques help enhance coverage of the code and can quickly identify items that do not follow the security best practices. The following phases and associated tools were used during the assessment:

    • Research into architecture and purpose.

    • Differences analysis using GitLens to have a proper view of the differences between the mentioned commits

    • Graphing out functionality and programs logic/connectivity/functions along with state changes

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:
EXPLOITABILITY 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: genius-contracts-solana
(b) Assessed Commit ID: d3b131f
(c) Items in scope:
  • genius-contracts-solana/programs/genius/src/constant.rs
  • genius-contracts-solana/programs/genius/src/error.rs
  • genius-contracts-solana/programs/genius/src/instructions/accept_authority.rs
↓ Expand ↓
Out-of-Scope: Third party dependencies and economic attacks.
Remediation Commit ID:
Out-of-Scope: New features/implementations after the remediation commit IDs.

6. Assessment Summary & Findings Overview

Critical

0

High

0

Medium

0

Low

2

Informational

2

Security analysisRisk levelRemediation Date
Incorrect usage of Pyth price data without considering confidence intervalLowSolved - 12/03/2024
Orchestrators have full access to bridge fundsLowRisk Accepted - 12/16/2024
Users cannot cancel token bridge without orchestrator consentInformationalAcknowledged - 12/16/2024
Missing validation for src_chain_id and dest_chain_id in create_orderInformationalSolved - 12/03/2024

7. Findings & Tech Details

7.1 Incorrect usage of Pyth price data without considering confidence interval

//

Low

Description

The create_order function is responsible for creating a new order in the program. It performs several critical tasks, including:

  • Validating the stability of the USDC price obtained from the Pyth Network.

  • Ensuring that sufficient fees are provided for cross-chain operations.

  • Initializing and populating the order with details such as the source and destination chains, token amounts, and involved parties.

  • Transferring USDC from the user’s token account to a vault account for order processing.


During the price validation step, the function fetches the USDC price using the Pyth price feed and adjusts it based on the feed's exponent value:


create_order.rs:

        msg!("deposit USDC amount: {:?}", amount);
        let price_update = &mut ctx.accounts.price_update;
        let feed_id: [u8; 32] = get_feed_id_from_hex(FEED_ID)?;
        let price = price_update.get_price_no_older_than(&Clock::get()?, MAXIMUM_AGE, &feed_id)?;

        // Adjust price to floating-point by scaling with 10^exponent
        let adjusted_price: f64 = (price.price as f64) * 10f64.powi(price.exponent);

However, this implementation does not consider the conf (confidence interval) parameter provided by Pyth, which represents the uncertainty range in the reported price. Ignoring conf might lead to decisions based on potentially unreliable price data, especially during periods of high market volatility.


By not incorporating the conf parameter, the program exposes itself to risks where the USDC/USD price might appear stable but has significant uncertainty. This could lead to:


  1. Depeg Exploitation: If USDC experiences a depeg and the confidence interval (conf) is not considered, the price could appear valid while being inaccurate due to high uncertainty. Users on the Solana network could exploit this by exchanging depegged USDC tokens on Solana for more valuable tokens on another network, effectively transferring the depeg losses to the program and its users.

  2. Systemic Risks Across Chains: As the function facilitates cross-chain operations, overlooking the confidence interval might propagate incorrect exchange rates between networks, leading to financial imbalances or exploits.

  3. Inaccurate Price Validation: In volatile market conditions, large conf values signal unreliable data. Ignoring this parameter leaves the program vulnerable to decisions based on incomplete or misleading price information.

BVSS
Recommendation

Modify the price validation logic to incorporate the conf parameter. This ensures that the price used for validation is reliable and falls within an acceptable range. For example:


create_order.rs:

let lower_bound = (price.price - price.conf) as f64 * 10f64.powi(price.exponent);
let upper_bound = (price.price + price.conf) as f64 * 10f64.powi(price.exponent);

// Validate price bounds
require!(lower_bound > 0.99, GeniusError::StableCoinPriceTooLow);
require!(upper_bound < MAX_ACCEPTABLE_PRICE, GeniusError::StableCoinPriceTooHigh);

By incorporating the confidence interval, the program can assess the validity of the price more accurately, mitigating risks associated with volatile or unreliable price data.

Remediation

SOLVED: The Genius team solved the issue by adding the conf parameter to the calculation.

Remediation Hash
References

7.2 Orchestrators have full access to bridge funds

//

Low

Description

In the Genius protocol, orchestrators are considered trusted actors, as they are controlled by the Genius team itself. Additionally, orchestrators can only be added to the system by an admin of the platform, ensuring they are pre-approved entities.


The program includes two functions, fill_order and remove_bridge_liquidity, that grant orchestrators direct access to the bridge’s funds stored in the vault:

  1. fill_order: This function allows orchestrators to withdraw USDC from the vault and transfer it to their own associated token accounts. Genius has declared that this function is intended to be executed via a Jito bundle, which would include an additional transaction transferring the funds from the orchestrator to the user. However, there is no restriction in place to enforce the use of Jito bundles, meaning an orchestrator could call fill_order independently and retain the funds without transferring them to the intended user. Furthermore, orchestrators have absolute control over the parameters of the order, including the amount to withdraw, the trader’s address, and the fees to be applied. This provides them with full discretion to manipulate the terms of each transaction.

  2. remove_bridge_liquidity: This function enables orchestrators to directly remove USDC liquidity from the bridge. It is primarily designed for managing the bridge’s reserves, but it similarly provides unrestricted withdrawal access.

Both functions can be seen in the snippets below:


fill_order.rs:

impl FillOrder<'_> {
    pub fn process_instruction(
        ctx: Context<Self>,
        amount: u64,
        seed: [u8; 32],
        trader: [u8; 32],
        src_chain_id: u32,
        dest_chain_id: u32,
        token_in: [u8; 32],
        fee: u64,
        min_amount_out: u64,
    ) -> Result<()> {
        msg!("withdraw USDC amount: {:?}", amount);

        //  check orchestrator is eligible
        let orchestrator_state = &ctx.accounts.orchestrator_state;

        require!(
            orchestrator_state.authorized == true,
            GeniusError::IllegalOrchestrator
        );

        if ctx.accounts.order.status != OrderStatus::Unexistant {
            return err!(GeniusError::OrderAlreadyExists);
        }

        let order = &mut ctx.accounts.order;

        order.amount_in = amount;
        order.seed = seed;
        order.trader = trader;
        order.receiver = ctx.accounts.receiver.key().to_bytes();
        order.src_chain_id = src_chain_id;
        order.dest_chain_id = dest_chain_id;
        order.token_in = token_in;
        order.fee = fee;
        order.status = OrderStatus::Filled;
        order.min_amount_out = min_amount_out;
        order.token_out = ctx.accounts.token_out.key().to_bytes();

        let global_state = &ctx.accounts.global_state;
        let current_balance = get_vault_balance(&ctx.accounts.ata_vault)?;

        let withdraw_amount = amount - order.fee;
        require!(
            balance_within_threshold(
                current_balance,
                current_balance - withdraw_amount,
                global_state.rebalance_threshold,
                ctx.accounts.asset.reserved_fees
            ),
            GeniusError::NeedRebalance
        );

        // Transfer USDC from vault to orchestrator
        token_transfer_with_signer(
            ctx.accounts.ata_vault.to_account_info().clone(),
            ctx.accounts.vault.to_account_info().clone(),
            ctx.accounts.ata_orchestrator.to_account_info().clone(),
            ctx.accounts.token_program.to_account_info().clone(),
            &[&[VAULT_SEED, &[ctx.bumps.vault]]],
            withdraw_amount,
        )
    }
}

remove_bridge_liquidity.rs:

impl RemoveBridgeLiquidity<'_> {
    pub fn process_instruction(ctx: Context<Self>, amount: u64) -> Result<()> {
        let orchestrator_state = &ctx.accounts.orchestrator_state;

        require!(
            orchestrator_state.authorized == true,
            GeniusError::IllegalOrchestrator
        );

        let current_balance = get_vault_balance(&ctx.accounts.ata_vault)?;

        require!(
            balance_within_threshold(
                current_balance,
                current_balance
                    .checked_sub(amount)
                    .ok_or(GeniusError::InvalidAmount)?,
                ctx.accounts.global_state.rebalance_threshold,
                ctx.accounts.asset.reserved_fees
            ),
            GeniusError::NeedRebalance
        );

        // Transfer USDC from vault to orchestrator
        token_transfer_with_signer(
            ctx.accounts.ata_vault.to_account_info().clone(),
            ctx.accounts.vault.to_account_info().clone(),
            ctx.accounts.ata_orchestrator.to_account_info().clone(),
            ctx.accounts.token_program.to_account_info().clone(),
            &[&[VAULT_SEED, &[ctx.bumps.vault]]],
            amount,
        )
    }
}

Despite the safeguards of trusted orchestrator designation and admin-only additions, concentrating full control of funds into the orchestrators’ hands introduces systemic risk if trust is compromised or if an orchestrator account is exploited.


We can mention the next results as impact for the system:

  • Malicious orchestrators: A rogue or compromised orchestrator could withdraw all funds, leading to a complete loss of bridge liquidity.

  • Operational risk: Unintentional errors by orchestrators, such as withdrawing excessive funds, could disrupt the bridge's ability to process user transactions.

  • Single point of failure: A central point of control over funds introduces a high-risk vector for attacks or misuse.

  • Jito bundle bypass: The lack of enforcement for Jito bundles means orchestrators could misuse the fill_order function to retain funds without completing the intended user transfer.

  • Loss of user trust: A significant breach or misuse could cause reputational damage to the protocol and reduce user confidence in the system.

BVSS
Recommendation

To mitigate the risks mentioned above, consider implementing all of the following solutions:


  1. Fill order and transfer to user in the same transaction: Bundle multiple transactions in the Jito Bundle, as initially expected. In one of the transactions, include 2 instructions. The first instruction is fill_order and the next instruction will transfer the corresponding tokens from the Orchestrator to the final user. Additionally, modify the fill_order instruction to require that the next instruction is effectively an instruction that transfers an amount > min_amount_out of token_out tokens to the final user. This way, the Orchestrator can't use fill_order without transferring the tokens to the user.

  2. Cross-Chain Messaging System: To mitigate the risk associated with orchestrators having complete control over the parameters of the orders in the fill_order function, consider implementing a cross-chain messaging protocol, such as Wormhole, that can handle order parameters off-chain in a verifiable and secure manner. By utilizing cross-chain messaging, you can decouple critical order parameters from orchestrator discretion, ensuring that the parameters are agreed upon transparently and independently across different chains, reducing the risk of manipulation.

  3. Withdrawal Limits: Implement limits on the amount that can be withdrawn in a single transaction or within a specific time period.

  4. Enhanced Monitoring: Add on-chain monitoring and alerting for large withdrawals to notify stakeholders immediately.

  5. Audit Trails: Maintain detailed logs of orchestrator actions on-chain to provide transparency and accountability. These logs should include withdrawal amounts, timestamps, and orchestrator identities.

  6. Periodic Reviews: Conduct periodic reviews of orchestrator activity and ensure compliance with protocol policies.

By implementing these measures, Genius can reduce the risks associated with centralized fund access, enforce the proper usage of fill_order, and maintain user trust and system integrity.

Remediation

RISK ACCEPTED: The Genius team accepted the risk of this finding.

7.3 Users cannot cancel token bridge without orchestrator consent

//

Informational

Description

In the Genius protocol, users rely on orchestrators to manage the cancellation of token bridge transactions. The WithdrawStableCoin function, which is part of the bridge liquidity system, is governed by the orchestrator. Users who wish to cancel or withdraw tokens via the bridge cannot do so independently. They are required to have the orchestrator's consent for the transaction to proceed.


The mentioned situation can be seen in the snippet below, where we can see that both signatures from user and orchestrator are required, and that the orchestrator state is derived from the orchestrator account:

#[derive(Accounts)]
pub struct WithdrawStableCoin<'info> {
    //  fee payer
    #[account(mut)]
    pub user: Signer<'info>,

    //  need to check eligible withdraw
    pub orchestrator: Signer<'info>,

    //  Global state
    #[account(
        seeds = [GLOBAL_SEED],
        bump,
        constraint = global_state.frozen == false @GeniusError::GlobalStateFrozen
    )]
    pub global_state: Box<Account<'info, GlobalState>>,

    //  Asset
    #[account(
        seeds = [ASSET_SEED],
        bump,
    )]
    pub asset: Box<Account<'info, Asset>>,

    //  Stores orchestrator info
    #[account(
        seeds = [orchestrator.key().as_ref(), ORCHESTRATOR_SEED],
        bump,
    )]
    pub orchestrator_state: Box<Account<'info, OrchestratorState>>,

    //  Needed to check vault authority
    #[account(	
        seeds = [VAULT_SEED],	
        bump,	
    )]
    /// CHECK: This is not dangerous because we don't read or write from this account
    pub vault: AccountInfo<'info>,

    //  USDC ata of vault
    #[account(
        mut, 
        associated_token::mint = usdc_mint, 
        
        //  Authority is set to vault
        associated_token::authority = vault,
    )]
    pub ata_vault: Box<Account<'info, TokenAccount>>,

    //  destination token account of user
    #[account(
        init_if_needed,
        payer = user,
        associated_token::mint = usdc_mint, 
        associated_token::authority = user,
    )]
    pub ata_user: Box<Account<'info, TokenAccount>>,

    // The mint of $USDC because it's needed from above ⬆ token::mint = ...
    #[account(
        address = global_state.base_mint,
    )]
    pub usdc_mint: Box<Account<'info, Mint>>,

    pub token_program: Program<'info, Token>,
    pub associated_token_program: Program<'info, AssociatedToken>,
    pub system_program: Program<'info, System>,
}

impl WithdrawStableCoin<'_> {
    pub fn process_instruction(ctx: Context<Self>, amount: u64) -> Result<()> {
        msg!("withdraw usdc amount: {:?}", amount);

        //  check orchestrator is eligible
        let orchestrator_state = &ctx.accounts.orchestrator_state;

        require!(
            orchestrator_state.authorized == true,
            GeniusError::IllegalOrchestrator
        );

        let global_state = &ctx.accounts.global_state;
        let current_balance = get_vault_balance(&ctx.accounts.ata_vault)?;

        require!(
            balance_within_threshold(
                current_balance,
                current_balance - amount,
                global_state.rebalance_threshold,
                ctx.accounts.asset.reserved_fees
            ),
            GeniusError::NeedRebalance
        );

        // Transfer USDC from vault to user
        token_transfer_with_signer(
            ctx.accounts.ata_vault.to_account_info().clone(),
            ctx.accounts.vault.to_account_info().clone(),
            ctx.accounts.ata_user.to_account_info().clone(),
            ctx.accounts.token_program.to_account_info().clone(),
            &[&[VAULT_SEED, &[ctx.bumps.vault]]],
            amount,
        )
    }
}

This creates a scenario where users have no control over their token bridge transactions once initiated. The orchestrator is the only entity that can approve or cancel these transactions, placing complete control in their hands. This means that if a user decides to cancel a transaction or modify the bridge transaction, the orchestrator’s authorization is mandatory.


The orchestrator's power to approve or reject token bridge cancellations exposes users to potential risks, such as:

  • Delays in transaction processing: Users are dependent on the orchestrator to approve or cancel their bridge transactions, and delays from the orchestrator could result in missed opportunities or unwanted transactions.

  • Centralization of control: Since the orchestrator holds exclusive control over bridge cancellations, this introduces a centralized point of failure. If the orchestrator is unavailable or malicious, users could lose the ability to cancel or modify transaction

BVSS
Recommendation

To mitigate the mentioned risks, consider the following improvements:


  1. Automated cancellation mechanism: Introduce an automatic cancellation mechanism for token bridges that can be triggered by specific conditions, reducing reliance on orchestrator approval for cancellations.

  2. Increased transparency and auditing: Implement logging and auditing of all bridge transactions, including cancellation requests, to provide transparency about orchestrator actions and the reasons behind transaction rejections.

  3. Clear user communication: Notify users when their cancellation requests are pending approval, providing an estimated timeframe for the orchestrator's response and clear messaging regarding the status of their transactions.

  4. Backup orchestrators and failover protocols: Ensure the availability of backup orchestrators to approve or cancel transactions in case the primary orchestrator becomes unavailable or is unresponsive.

By decentralizing control over token bridge transactions, improving transparency, and ensuring failover mechanisms, Genius can reduce the risks associated with users' inability to independently cancel or modify token bridge transactions.

Remediation

ACKNOWLEDGED: The Genius team acknowledged this finding.

7.4 Missing validation for src_chain_id and dest_chain_id in create_order

//

Informational

Description

The create_order function is responsible for creating a new order for a user to transfer tokens across different blockchains. It checks the deposit amount, verifies that the user has sufficient fees, and sets the details for the order, including the source and destination chains (src_chain_id and dest_chain_id). However, the function does not verify that the source and destination chains are distinct. As we can see in the snippet below, the values for src_chain_id and dest_chain_id are directly assigned from the function parameters without any validation:

    pub fn process_instruction(
        ctx: Context<Self>,
        amount: u64,
        seed: [u8; 32],
        trader: [u8; 32],
        receiver: [u8; 32],
        src_chain_id: u32,
        dest_chain_id: u32,
        token_in: [u8; 32],
        fee: u64,
        min_amount_out: u64,
        token_out: [u8; 32],
    ) -> Result<()> {
        msg!("deposit USDC amount: {:?}", amount);
        let price_update = &mut ctx.accounts.price_update;
        let feed_id: [u8; 32] = get_feed_id_from_hex(FEED_ID)?;
        let price = price_update.get_price_no_older_than(&Clock::get()?, MAXIMUM_AGE, &feed_id)?;

        // Adjust price to floating-point by scaling with 10^exponent
        let adjusted_price: f64 = (price.price as f64) * 10f64.powi(price.exponent);

        require!(adjusted_price > 0.99, GeniusError::StableCoinPriceTooLow);

        if ctx.accounts.order.status != OrderStatus::Unexistant {
            return err!(GeniusError::OrderAlreadyExists);
        }

        let min_fee = ctx.accounts.target_chain_min_fee.min_fee;
        if min_fee > fee {
            return err!(GeniusError::InsufficientFees);
        }

        ctx.accounts.order.amount_in = amount;
        ctx.accounts.order.seed = seed;
        ctx.accounts.order.trader = trader;
        ctx.accounts.order.receiver = receiver;
        ctx.accounts.order.src_chain_id = src_chain_id;
        ctx.accounts.order.dest_chain_id = dest_chain_id;
        ctx.accounts.order.token_in = token_in;
        ctx.accounts.order.fee = fee;
        ctx.accounts.order.status = OrderStatus::Created;
        ctx.accounts.order.min_amount_out = min_amount_out;
        ctx.accounts.order.token_out = token_out;

        ctx.accounts.asset.unclaimed_fees += fee;

        let signer = &ctx.accounts.signer;
        let orchestrator = &ctx.accounts.orchestrator;
        let orchestrator_state = &ctx.accounts.orchestrator_state;

        // Convert signer key and orchestrator key to [u8; 32] for comparison
        let signer_key_bytes: [u8; 32] = signer.key().to_bytes();
        let orchestrator_key_bytes: [u8; 32] = orchestrator.key().to_bytes();
        require!(
            signer_key_bytes == trader || signer_key_bytes == orchestrator_key_bytes,
            GeniusError::UnauthorizedSigner
        );
        require!(
            orchestrator_state.authorized == true,
            GeniusError::IllegalOrchestrator
        );

        // Transfer USDC from orchestrator to vault
        token_transfer_user(
            ctx.accounts.ata_signer.to_account_info().clone(),
            ctx.accounts.signer.to_account_info().clone(),
            ctx.accounts.ata_vault.to_account_info().clone(),
            ctx.accounts.token_program.to_account_info().clone(),
            amount,
        )
    }

Without checking that the source and destination chains are different, the user may end up paying fees for a transaction that is essentially redundant, leading to unnecessary costs. This could be especially problematic in a system where chain-to-chain transfers are intended, and users may mistakenly believe their funds are being transferred across chains when they are not.

BVSS
Recommendation

Consider adding a validation step that checks whether the src_chain_id and dest_chain_id are distinct. If they are the same, the function should reject the transaction or notify the user about the issue.

Remediation

SOLVED: The Genius team solved the issue by adding a check to validate that both chains are different.

Remediation Hash

8. Automated Testing

Description

Halborn used automated security scanners to assist with the 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 reviewers 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.

Results

ID

package

Short Description

RUSTSEC-2022-0093

ed25519-dalek

Double Public Key Signing Function Oracle Attack on ed25519-dalek

RUSTSEC-2024-0344

curve25519-dalek

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

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.

© Halborn 2025. All rights reserved.