linera-io/linera-protocol · critical · EvmExecutionError
The balances are incoherent for address {0}, balances {1}, {
Error message
The balances are incoherent for address {0}, balances {1}, {2} What it means
Raised by the EVM adapter's ContractDatabase::check_balance during commit_changes for every writable, non-faucet contract account: the Linera-side balance of the contract's owner (read via the Linera runtime) must equal the balance the EVM (revm) computed for that address. The EVM bridge mirrors ETH balances in Linera's native accounting; any divergence means the EVM execution changed a contract's value without the corresponding Linera transfer (or vice versa), so the block is rejected rather than committing incoherent state.
Source
Thrown at linera-execution/src/evm/database.rs:604
pub fn new(runtime: Runtime) -> Self {
Self {
inner: InnerDatabase::new(runtime),
modules: Arc::new(Mutex::new(HashMap::new())),
}
}
pub fn lock_runtime(&self) -> std::sync::MutexGuard<'_, Runtime> {
self.inner.lock_runtime()
}
/// Balances of the contracts have to be checked when
/// writing. There is a balance in Linera and a balance
/// in EVM and they have to be coherent.
fn check_balance(&self, address: Address, revm_balance: U256) -> Result<(), ExecutionError> {
let mut runtime = self.inner.runtime.lock().unwrap();
let owner = address.into();
let linera_balance: U256 = runtime.read_owner_balance(owner)?.into();
ensure!(
linera_balance == revm_balance,
EvmExecutionError::IncoherentBalances(address, linera_balance, revm_balance)
);
Ok(())
}
/// Effectively commits changes to storage.
pub fn commit_contract_changes(
&self,
account: &revm_state::Account,
) -> Result<(), ExecutionError> {
let mut runtime = self.inner.runtime.lock().unwrap();
let mut batch = Batch::new();
let key_prefix = get_category_key(KeyCategory::Storage);
let key_info = get_category_key(KeyCategory::AccountInfo);
if account.is_selfdestructed() {
batch.delete_key_prefix(key_prefix);
batch.put_key_value(key_info, &AccountInfo::default())?;View on GitHub (pinned to 6c226ddcb3)
Solutions
- Ensure the contract's ETH transfers only use the supported paths (the bridge's transfer precompile at address 0x0b / the faucet address flow)
- Reproduce with the contract on a local testnet and inspect which transaction desynchronizes the balance (the error prints address, Linera balance, and revm balance)
- Update to the latest linera-protocol version — EVM balance-coherency fixes have landed repeatedly
- If it reproduces on current versions with a minimal contract, report it as a bug with the bytecode and transaction
Defensive patterns
Strategy: try-catch
Validate before calling
// In app tests, run the ETH-transfer scenario on a local testnet first; the adapter's // check fires at commit time, so a dry-run execution surfaces the incoherence before mainnet.
Type guard
fn is_incoherent_balance(err: &ExecutionError) -> Option<(&Address, &U256, &U256)> {
match err {
ExecutionError::EvmError(evm_error!::IncoherentBalances(addr, linera, evm)) => Some((addr, linera, evm)),
_ => None,
}
} Try / catch
match client.execute_operations(ops, vec![]).await {
Err(e) if is_incoherent_balance(&e).is_some() => {
// Address and both balances are in the error: log them, halt the deploy,
// and audit the contract's ETH-transfer paths.
tracing::error!(error = %e, "EVM/Linera balance divergence; blocking further submissions");
return Err(e.into());
}
other => other,
} Prevention
- Keep contract ETH transfers on the bridge-supported paths (transfer precompile/faucet address); avoid selfdestruct-value and raw value-sending patterns
- Test any value-moving contract on a Linera devnet where check_balance failures surface before production
- Stay on the latest linera-protocol release — EVM balance coherency has ongoing fixes
When it happens
Trigger: An EVM application moves ETH in a way the bridge does not mirror back into Linera balances: raw value transfers on paths outside the faucet precompile, selfdestruct value-recovery flows, precompile transfers, or bugs in the balance wiring. FAUCET_ADDRESS (0x...4000) is explicitly exempt from checks; ordinary contract accounts are not.
Common situations: Porting Solidity contracts that rely on value semantics Linera's bridge does not support; running a newer/older contract bytecode against a linera-execution version whose balance accounting changed; genuine protocol bugs in the EVM adapter itself.
Related errors
- It is illegal to call function execute_message from an opera
- It is illegal to call function process_streams from an opera
- It is illegal to call function summarize_events from an oper
- It is illegal to call function instantiate from an operation
- The function {0} is being called but is missing from the byt
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/e46def8a68bccb28.
Report an issue: GitHub.