🔐 Security & Audits
Security

Advanced Measure

💡

This page documents AggreLend’s in-protocol security measures and shows how we implement CPI (Cross-Program Invocation) guards in Anchor-based Solana programs.

Overview

AggreLend has hired experienced, security-minded Rust engineers to implement top-of-the-line protections directly into the protocol. Below we outline the measures we take. While some details get technical (and show real smart-contract code), we’ve worked to keep explanations clear and practical.

At a high level:

  • Every instruction defensively checks the transaction’s instruction sysvar, enabling full visibility into the entire transaction context (CTX)—not just the current instruction.
  • If we detect an unknown or untrusted program involved—either because our instruction is reached via CPI or because the same transaction includes foreign program instructions (e.g., flash-loan orchestration)—we abort the transaction.
  • This approach prevents classes of exploits seen in Solana DeFi (e.g., Mango Markets – Oct 11, 2022; Nirvana Finance – Jul 28, 2022; Solvent SVT Flash Loan – Aug 26, 2023) where attackers drive the victim program from their own malicious program.

CPI Check Macro (Rust)

We invoke a CPI guard macro at the beginning of every entrypoint, passing the full transaction context:

check_cpi!(ctx); What the macro does Enumerates the entire transaction’s instructions for diagnostics.

Captures metadata about the current instruction (index, program_id, stack height).

Rejects execution if the instruction is reached via CPI (or via self-reentry).

Rust
#[macro_export]
macro_rules! check_cpi {
    ($ctx:expr) => {{
        use ::anchor_lang::solana_program::{
            instruction::{get_stack_height, Instruction, TRANSACTION_LEVEL_STACK_HEIGHT},
            sysvar::instructions::{load_current_index_checked, load_instruction_at_checked},
            msg,
        };
 
        let sysvar_ai = &$ctx.accounts.instruction_sysvar_account;
 
        // Current-instruction metadata
        let cur_idx = load_current_index_checked(sysvar_ai)? as usize;
        let cur_ix: Instruction = load_instruction_at_checked(cur_idx, sysvar_ai)?;
        let stack = get_stack_height();
 
        // 1) Dump every instruction in the transaction (for observability)
        let mut ix_i = 0usize;
        loop {
            match load_instruction_at_checked(ix_i, sysvar_ai) {
                Ok(ix) => {
                    // Truncate data preview to 8 bytes to keep logs short
                    let data_preview = {
                        let mut buf = [0u8; 8];
                        for (i, b) in ix.data.iter().take(8).enumerate() {
                            buf[i] = *b;
                        }
                        buf
                    };
                    msg!(
                        "IX #{}, program: {}, accts: {}, data[0..8]: 0x{:02X?}",
                        ix_i,
                        ix.program_id,
                        ix.accounts.len(),
                        data_preview
                    );
                    ix_i += 1;
                }
                Err(::anchor_lang::solana_program::program_error::ProgramError::InvalidArgument) => {
                    // End of instruction list
                    break;
                }
                Err(e) => return Err(e.into()),
            }
        }
 
        // 2) Extra diagnostics about the current instruction
        msg!(
            "Current instruction index: {}, program_id: {}, stack height: {}",
            cur_idx,
            cur_ix.program_id,
            stack
        );
 
        // 3) CPI guard logic
        if cur_ix.program_id != crate::ID || stack > TRANSACTION_LEVEL_STACK_HEIGHT {
            msg!("FORBIDDEN: instruction came via CPI (or self-reentry)!");
            return ::anchor_lang::err!(crate::error::ErrorCode::DoNotHavePermission);
        }
    }};
}
 

Why this matters: Many historical exploits begin by driving the victim program from an attacker-controlled program. If our instruction is reached as a sub-instruction (via CPI) by a foreign program, we halt execution immediately.

Runtime Logs (Example) These are representative logs from AggreLend’s CPI checks—showing awareness of the entire transaction, not just our instruction:

Rust
(AGGREbma2Gi9unS1mPptAcG4HmkMTLNmqcunYaSSf46b) instruction
> Program log: Instruction: Withdraw
> Program log: IX #0, program: ComputeBudget111111111111111111111111111111, accts: 0, data[0..8]: 0x[03, BC, A3, 08, 00, 00, 00, 00]
> Program log: IX #1, program: ComputeBudget111111111111111111111111111111, accts: 0, data[0..8]: 0x[02, 20, A1, 07, 00, 00, 00, 00]
> Program log: IX #2, program: AGGREbma2Gi9unS1mPptAcG4HmkMTLNmqcunYaSSf46b, accts: 16, data[0..8]: 0x[B7, 12, 46, 9C, 94, 6D, A1, 22]
> Program log: Current instruction index: 2, program_id: AGGREbma2Gi9unS1mPptAcG4HmkMTLNmqcunYaSSf46b, stack height: 1
> Invoking
Drift V2 Program
  > Program log: Instruction: Withdraw

With this visibility, we distinguish a normal user flow (e.g., compute budget adjustments then AggreLend) from a malicious flow (a foreign program that tries to route into AggreLend). In the latter case, we reject the transaction before any state changes occur.

Preventing Same-Transaction Orchestration (e.g., Flash Loans) Attackers often orchestrate multi-step sequences within a single transaction, mixing the victim protocol with unrelated programs (e.g., a flash-loan source). We defend by rejecting transactions that include any program other than our program and Solana’s ComputeBudget program.

Rust
use ::anchor_lang::solana_program::{
    msg,
    program_error::ProgramError,
    sysvar::instructions::load_instruction_at_checked,
};
 
pub fn reject_foreign_programs(sysvar_ai: &anchor_lang::prelude::AccountInfo) -> anchor_lang::Result<()> {
    let mut i = 0usize;
    loop {
        match load_instruction_at_checked(i, sysvar_ai) {
            Ok(ix) => {
                // Only allow our program and the ComputeBudget program
                if ix.program_id != crate::ID
                    && ix.program_id != ::anchor_lang::pubkey!("ComputeBudget111111111111111111111111111111")
                {
                    msg!("FORBIDDEN: tx contains instruction #{} from {}", i, ix.program_id); // [!code highlight]
                    return ::anchor_lang::err!(crate::error::ErrorCode::DoNotHavePermission);
                }
                i += 1;
            }
            Err(ProgramError::InvalidArgument) => break, // End of list
            Err(e) => return Err(e.into()),
        }
    }
    Ok(())
}

Effect: If the same transaction includes a flash-loan or any unrelated program call, we abort. This single rule alone would have prevented the vast majority of recent Solana DeFi attacks.

Example references Crema Finance flash-loan incident: https://solscan.io/tx/5B4QXpMfpDpaX8dg2GF5DVLz9dAiZz1sjPL45wgP7X1o9fpdgCvYKi2FHEosSQBS63uDsos37AyrKC1a4YbKohGv (opens in a new tab)

Mango Markets – Oct 11, 2022; Nirvana Finance – Jul 28, 2022; Solvent (SVT Flash Loan) – Aug 26, 2023

Policy & Integration Notes There is no legitimate reason for a wallet to take a flash loan in the same transaction as interacting with a simple AggreLend lending operation. Transactions that attempt this are rejected by design.

These checks are fully decentralized and enforced on-chain. There is no centralized allowlist at execution time for end users.

If you want to integrate AggreLend via CPI in your own DeFi program:

You may test on devnet/testnet freely.

For mainnet, please contact the AggreLend dev team at (LINK) to be added to the trusted-protocols list after audit and KYB verification.

Recommendation to Other Protocols Even rigorously audited code can be vulnerable at integration boundaries. We recommend every Solana DeFi program implement similar transaction-context guards. Your system is only as strong as its weakest interface; these checks eliminate entire exploit paths proactively.