{"record":{"id":"0ec6151a12180e7b","repo":"linera-io/linera-protocol","slug":"non-zero-transfer-precompile","errorCode":null,"errorMessage":"Non-zero transfer precompile","messagePattern":"Non-zero transfer precompile","errorType":"validation","errorClass":"EvmExecutionError","httpStatus":null,"severity":"error","filePath":"linera-execution/src/evm/revm.rs","lineNumber":1096,"sourceCode":"    /// The precompile calls do not have associated transfers.\n    /// For other contract calls, the corresponding transfer\n    /// is executed in Linera.\n    ///\n    /// Note that in the EVM transferring ethers is the same\n    /// as calling a function. In Linera, transferring native\n    /// tokens and calling a function are different operations.\n    /// However, the block is accepted completely or not at all.\n    /// Therefore, we can ensure the atomicity of the operations.\n    fn call_or_fail(\n        &self,\n        _context: &mut ContractCtx<'_, Runtime>,\n        inputs: &CallInputs,\n    ) -> Result<Option<CallOutcome>, ExecutionError> {\n        let is_precompile = self.precompile_addresses.contains(&inputs.target_address);\n        let is_first_call = inputs.target_address == self.contract_address;\n        if is_precompile {\n            if let CallValue::Transfer(value) = inputs.value {\n                ensure!(\n                    value == U256::ZERO,\n                    EvmExecutionError::NonZeroTransferPrecompile\n                );\n            }\n        }\n        if is_precompile || is_first_call {\n            // Precompile calls are handled by the precompile code.\n            return Ok(None);\n        }\n        // Handling the balances.\n        if let CallValue::Transfer(value) = inputs.value {\n            if value != U256::ZERO {\n                let source: AccountOwner = inputs.caller.into();\n                let owner: AccountOwner = inputs.bytecode_address.into();\n                let mut runtime = self.db.lock_runtime();\n                let amount = Amount::try_from(value).map_err(EvmExecutionError::from)?;\n                let chain_id = runtime.chain_id()?;\n                let destination = Account { chain_id, owner };","sourceCodeStart":1078,"sourceCodeEnd":1114,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-execution/src/evm/revm.rs#L1078-L1114","documentation":"call_or_fail inspects every EVM internal call: if the target address is one of the registered Linera precompile addresses and the call carries a CallValue::Transfer with a non-zero amount, execution fails with NonZeroTransferPrecompile (revm.rs:1096). Precompiles are pure system endpoints and cannot receive native value.","triggerScenarios":"Solidity code performing precompile.call{value: amount}(data), or any payable wrapper or fallback that attaches msg.value to every outbound external call, where the target is a Linera precompile address such as 0x0b.","commonSituations":"Contracts with catch-all forwarding logic that forwards msg.value along; multisend and multicall patterns that preserve value on every subcall; copy-pasted Ethereum code that tips precompiles when ported to Linera; payable functions that blindly relay callvalue.","solutions":["Strip the value: call the precompile as precompile.call(data) with no value option","Mark the calling Solidity function nonpayable or refund excess msg.value before dispatching subcalls","If value must move, send it in a separate explicit transfer to a contract account, never to a precompile address"],"exampleFix":"// before: value forwarded to a precompile\n(bool ok, ) = PRECOMPILE.call{value: msg.value}(data);\n\n// after: zero-value call to the precompile, funds handled separately\n(bool ok, ) = PRECOMPILE.call(data);\nif (msg.value > 0) payable(recipient).transfer(msg.value);","handlingStrategy":"validation","validationCode":"// Solidity: never attach value to a precompile call\nfunction callPrecompile(bytes memory data) internal returns (bytes memory) {\n    (bool ok, bytes memory out) = PRECOMPILE.call(data); // no value attached\n    require(ok, PrecompileCallFailed());\n    return out;\n}","typeGuard":"fn is_nonzero_transfer_precompile(err: &ExecutionError) -> bool {\n    matches!(\n        err,\n        ExecutionError::EvmError(EvmExecutionError::NonZeroTransferPrecompile)\n    )\n}","tryCatchPattern":"match evm_call(input) {\n    Ok(out) => out,\n    Err(ref e) if is_nonzero_transfer_precompile(e) => {\n        // deterministic misuse: strip the value and fail loudly, do not blind-retry\n        return Err(anyhow!(\"precompile called with non-zero value; remove the value option\"));\n    }\n    Err(e) => return Err(e.into()),\n}","preventionTips":["Keep precompile wrappers in nonpayable functions; reject msg.value > 0 in dispatchers","Audit forwarder and multicall code for value forwarding on outbound calls","Send native value only to contract accounts, never to precompile addresses"],"tags":["evm","solidity","precompile","value-transfer","linera"],"backgroundTag":"precompile-call-rejected","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}