linera-io/linera-protocol · error · ExecutionError

IncorrectTransferAmount

IncorrectTransferAmount

Error message

ExecutionError::IncorrectTransferAmount

What it means

Native-token transfers must carry a strictly positive amount; this error is raised when amount is Amount::ZERO. It fires after owner authentication succeeds but before self.debit runs, so no balance changes and the transaction is rejected cleanly.

Source

Thrown at linera-execution/src/system.rs:751

        source: AccountOwner,
        recipient: Account,
        amount: Amount,
    ) -> Result<Option<OutgoingMessage>, ExecutionError> {
        if source == AccountOwner::CHAIN {
            let authenticated_owner =
                authenticated_owner.ok_or(ExecutionError::UnauthenticatedTransferOwner)?;
            ensure!(
                self.ownership.get().await?.is_owner(&authenticated_owner),
                ExecutionError::UnauthenticatedTransferOwner
            );
        } else {
            ensure!(
                authenticated_owner == Some(source)
                    || authenticated_application_id.map(AccountOwner::from) == Some(source),
                ExecutionError::UnauthenticatedTransferOwner
            );
        }
        ensure!(
            amount > Amount::ZERO,
            ExecutionError::IncorrectTransferAmount
        );
        self.debit(&source, amount).await?;
        self.credit_or_send_message(source, recipient, amount).await
    }

    /// Claims `amount` from `source`'s account on `target_id` and transfers it to `recipient`.
    pub async fn claim(
        &mut self,
        authenticated_owner: Option<AccountOwner>,
        authenticated_application_id: Option<ApplicationId>,
        source: AccountOwner,
        target_id: ChainId,
        recipient: Account,
        amount: Amount,
    ) -> Result<Option<OutgoingMessage>, ExecutionError> {
        ensure!(

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Validate amount > Amount::ZERO before building or submitting the transfer operation.
  2. Trace where the amount was computed and look for integer division or premature rounding that can collapse it to zero.
  3. When converting user-entered amounts, compute in the smallest unit with checked arithmetic (try_mul/try_add) and reject non-positive results at the boundary.

Example fix

// before: amount parsed from user input can be zero
let amount = Amount::from_str(&input)?;
runtime.transfer(source, recipient, amount);

// after: reject non-positive amounts before touching the system API
let amount = Amount::from_str(&input)?;
ensure!(!amount.is_zero(), "transfer amount must be positive");
runtime.transfer(source, recipient, amount);
Defensive patterns

Strategy: validation

Validate before calling

ensure!(
    !amount.is_zero(),
    "transfer amount must be positive"
);
let _ = source; // then submit the Transfer operation

Type guard

fn is_incorrect_transfer_amount(e: &ExecutionError) -> bool {
    matches!(e, ExecutionError::IncorrectTransferAmount)
}

Try / catch

match result {
    Err(ExecutionError::IncorrectTransferAmount) => {
        // Amount was zero: fix the amount computation/input, then resubmit.
    }
    Err(e) => return Err(e.into()),
    Ok(value) => { /* ... */ }
}

Prevention

When it happens

Trigger: Submitting SystemOperation::Transfer with amount zero; an application calling runtime.transfer(source, destination, Amount::ZERO); amounts computed from user input, rounding, or unit conversion that underflowed to zero (e.g. integer division applied before multiplication when converting display units to the 10^-9 base unit).

Common situations: Parsing an empty or '0' amount string from CLI or GraphQL input; off-by-one or integer-division bugs producing 0; test or health-check transactions sent with a zero amount; unit-conversion mistakes between human-readable LLN units and the smallest supported unit.

Related errors


AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22). Data as JSON: /api/errors/bcb54c52c7d23ead. Report an issue: GitHub.