Unchecked CPI Return Remediation
How to fix CPI calls that ignore return values, preventing silent failures.
Unchecked CPI Return Remediation
Overview
Related Detector: Unchecked CPI Return Value
Unchecked CPI return values allow called programs to fail silently while the calling program continues execution with incorrect assumptions. The fix is to always propagate or explicitly check CPI return values.
Recommended Fix
Before (Vulnerable)
let _ = invoke(&transfer_ix, accounts); // Error discarded
update_state(accounts)?; // Proceeds even if transfer failed
After (Fixed)
invoke(&transfer_ix, accounts)?; // Error propagated
update_state(accounts)?; // Only runs if transfer succeeded
Alternative Mitigations
1. Explicit error handling
When you need custom error logic rather than simple propagation:
match invoke(&transfer_ix, accounts) {
Ok(()) => {
update_state(accounts)?;
}
Err(e) => {
msg!("Transfer failed: {:?}", e);
rollback_state(accounts)?;
return Err(e);
}
}
2. Conditional CPI with fallback
if let Err(e) = invoke(&primary_ix, accounts) {
msg!("Primary CPI failed, attempting fallback");
invoke(&fallback_ix, accounts)?;
}
3. Anchor pattern
Anchor’s typed CPI helpers return Result and the ? operator is the standard pattern:
token::transfer(cpi_ctx, amount)?;
Common Mistakes
Mistake 1: Using let _ = to suppress the warning
// WRONG: silences the compiler warning but ignores the error
let _ = invoke(&ix, accounts);
Mistake 2: Checking but not returning the error
// WRONG: logs the error but continues execution
if invoke(&ix, accounts).is_err() {
msg!("CPI failed");
// Missing: return Err(...);
}
// Execution continues with incorrect state
Mistake 3: Wrapping in ok() or unwrap_or_default()
// WRONG: converts error to success, hiding the failure
invoke(&ix, accounts).ok();
invoke(&ix, accounts).unwrap_or_default();