linera-io/linera-protocol · error · EvmExecutionError

Contracts cannot call themselves

Error message

Contracts cannot call themselves

What it means

The Linera contract runtime precompile exposed to EVM contracts rejects tryCallApplication when the target application id equals the caller's own id (revm.rs:616). This is a direct reentrancy guard: re-entering your own application through the runtime while its execution is in flight would bypass fuel and state-consistency accounting.

Source

Thrown at linera-execution/src/evm/revm.rs:616

            } => {
                let authenticated = true;
                let is_tracked = true;
                let grant = Resources::default();
                let send_message_request = SendMessageRequest {
                    destination,
                    authenticated,
                    is_tracked,
                    grant,
                    message,
                };
                let mut runtime = context.db().0.lock_runtime();
                runtime.send_message(send_message_request)?;
                Ok(vec![])
            }
            ContractRuntimePrecompile::TryCallApplication { target, argument } => {
                let authenticated = true;
                let mut runtime = context.db().0.lock_runtime();
                ensure!(
                    target != runtime.application_id()?,
                    EvmExecutionError::NoSelfCall
                );
                runtime.try_call_application(authenticated, target, argument)
            }
            ContractRuntimePrecompile::Emit { stream_name, value } => {
                let mut runtime = context.db().0.lock_runtime();
                let result = runtime.emit(stream_name, value)?;
                Ok(bcs::to_bytes(&result)?)
            }
            ContractRuntimePrecompile::ReadEvent {
                chain_id,
                stream_name,
                index,
            } => {
                let mut runtime = context.db().0.lock_runtime();
                runtime.read_event(chain_id, stream_name, index)
            }

View on GitHub (pinned to 6c226ddcb3)

Solutions

  1. Refactor: invoke the internal Solidity function directly instead of a tryCallApplication round-trip
  2. Guard the precompile call and revert early when target == own application id
  3. If recursion is genuinely needed, split the logic into two applications so the call is never self-directed

Example fix

// before: re-enter self through the runtime
runtime.tryCallApplication(appId, data); // appId == this application

// after: direct internal call, no runtime round-trip
handleInternally(data);
Defensive patterns

Strategy: validation

Validate before calling

// Solidity: refuse to call ourselves through the runtime precompile
function callApp(address target, bytes memory data) internal {
    require(target != OWN_APP_TARGET, NoSelfTarget());
    LineraRuntime(PRECOMPILE).tryCallApplication(target, data);
}

Type guard

fn is_no_self_call(err: &ExecutionError) -> bool {
    matches!(err, ExecutionError::EvmError(EvmExecutionError::NoSelfCall))
}

Try / catch

match runtime.try_call_application(target, arg) {
    Ok(out) => out,
    Err(ref e) if is_no_self_call(e) => {
        // self-call attempted: run the logic internally instead
        Ok(self.handle_internally(arg))
    }
    Err(e) => Err(e),
}

Prevention

When it happens

Trigger: Inside an EVM contract, calling the runtime precompile (address 0x0b) with ContractRuntimePrecompile::TryCallApplication where target equals the application id the contract itself is running as.

Common situations: Trying to reuse your own public entry point via the runtime instead of an internal Solidity function call; generic call-forwarding or fallback code that passes through whatever target it received (which may resolve to self); porting recursive application designs from other chains.

Related errors


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