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).
#[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:
(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.
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.