- Category: logic
- Severity: Medium
- Rule name:
unhandled_result
S009 flags expressions that produce a Result but never consume it — a Result-returning call used as a statement and discarded. The rule walks contract functions and reports calls whose Result value is dropped instead of being handled with ?, match, if let, or an explicit .unwrap() / .expect().
A discarded Result means an error path is silently ignored: the call may have failed, but execution continues as if it succeeded. In a contract, that turns a failed sub-operation into a success the caller trusts — the classic "silent failure masquerading as success," which can leave balances, allowances, or flags in an inconsistent state with no signal that anything went wrong.
#![no_std]
use soroban_sdk::{contract, contractimpl, Address, Env};
fn record_payment(env: &Env, from: &Address, amount: i128) -> Result<(), Error> {
// ... may fail ...
Ok(())
}
pub enum Error {
Failed,
}
#[contract]
pub struct Ledger;
#[contractimpl]
impl Ledger {
pub fn pay(env: Env, from: Address, amount: i128) {
from.require_auth();
// S009: the Result is discarded — a failure is silently ignored.
record_payment(&env, &from, amount);
}
}#![no_std]
use soroban_sdk::{contract, contractimpl, Address, Env};
fn record_payment(env: &Env, from: &Address, amount: i128) -> Result<(), Error> {
Ok(())
}
#[derive(Clone)]
pub enum Error {
Failed,
}
#[contract]
pub struct Ledger;
#[contractimpl]
impl Ledger {
pub fn pay(env: Env, from: Address, amount: i128) -> Result<(), Error> {
from.require_auth();
// Propagate the error with `?` instead of dropping it.
record_payment(&env, &from, amount)?;
Ok(())
}
}- Vector:
CVSS:3.1/AV:N/AC:H/PR:N/UI:N/S:U/C:N/I:H/A:N - Base score: 5.9
- Rating: Medium
Exploitation depends on forcing the ignored sub-operation to fail (higher complexity), but the consequence — committed state that assumes a failed step succeeded — is a high integrity impact.
- Handle every
Resultwith?to propagate, ormatch/if letto branch on the error. - If a failure is genuinely irrelevant, make the intent explicit (e.g.
let _ = ...;with a comment) rather than silently dropping it. - For cross-contract calls, use
try_invoke_contractand inspect the returnedResult.