{"record":{"id":"b249160934f48a43","repo":"linera-io/linera-protocol","slug":"failed-to-deserialize-operation-in-execute-opera","errorCode":null,"errorMessage":"Failed to deserialize `Operation` in execute_operation","messagePattern":"Failed to deserialize `Operation` in execute_operation","errorType":"panic","errorClass":null,"httpStatus":null,"severity":"critical","filePath":"linera-sdk/src/contract/mod.rs","lineNumber":64,"sourceCode":"                use $crate::util::BlockingWait as _;\n                $crate::contract::run_async_entrypoint::<$contract, _, _>(\n                    unsafe { &mut CONTRACT },\n                    move |contract| {\n                        let argument = $crate::serde_json::from_slice(&argument)\n                            .unwrap_or_else(|_| panic!(\"Failed to deserialize instantiation argument {argument:?}\"));\n\n                        contract.instantiate(argument).blocking_wait()\n                    },\n                )\n            }\n\n            fn execute_operation(operation: Vec<u8>) -> Vec<u8> {\n                use $crate::util::BlockingWait as _;\n                $crate::contract::run_async_entrypoint::<$contract, _, _>(\n                    unsafe { &mut CONTRACT },\n                    move |contract| {\n                        let operation = <$contract as $crate::abi::ContractAbi>::deserialize_operation(operation)\n                            .expect(\"Failed to deserialize `Operation` in execute_operation\");\n\n                        let response = contract.execute_operation(operation).blocking_wait();\n\n                        <$contract as $crate::abi::ContractAbi>::serialize_response(response)\n                            .expect(\"Failed to serialize `Response` in execute_operation\")\n                    },\n                )\n            }\n\n            fn execute_message(message: Vec<u8>) {\n                use $crate::util::BlockingWait as _;\n                $crate::contract::run_async_entrypoint::<$contract, _, _>(\n                    unsafe { &mut CONTRACT },\n                    move |contract| {\n                        let message: <$contract as $crate::Contract>::Message =\n                            $crate::bcs::from_bytes(&message)\n                                .expect(\"Failed to deserialize message\");\n","sourceCodeStart":46,"sourceCodeEnd":82,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-sdk/src/contract/mod.rs#L46-L82","documentation":"This is the contract-side boilerplate generated by linera_sdk::contract! (linera-sdk/src/contract/mod.rs). When the runtime dispatches execute_operation, the raw operation bytes are BCS-deserialized into the contract's declared Operation type and success is expected. The panic fires when the bytes do not decode into that type - i.e. the application that created the operation and this contract bytecode disagree on the Operation schema. The panic aborts the contract call and fails the executing block.","triggerScenarios":"An operation is posted through an application whose bytecode/ABI differs from the contract executing it: an upgraded application version changed the Operation type while old bytecode still handles calls, mismatched WASM modules published under the same application, or hand-crafted operation bytes sent via request-application/publish paths.","commonSituations":"Redeploying an application with modified Operation types but reusing the old application ID; publishing mismatched contract/service bytecode pairs; version drift between the dApp frontend (encoding operations) and the on-chain contract.","solutions":["Publish the application again so the operation sender and the handling contract share identical bytecode and Operation types.","Keep Operation (and all ABI types) in the application's shared crate and use that crate on both sides.","Never reuse an application ID after changing Operation; publish as a new application.","Add a unit test round-tripping your operations: bcs::to_bytes then from_bytes::<Operation>, to catch schema drift before deployment."],"exampleFix":"// before: frontend encodes with its own struct\nlet bytes = bcs::to_bytes(&MyOperationV1 { ... })?;\napp.call(bytes).await?; // contract expects MyOperationV2 -> panic\n\n// after: both sides use the shared ABI crate\nuse my_app::Operation;\nlet bytes = bcs::to_bytes(&Operation::Transfer { ... })?;\napp.call(bytes).await?;","handlingStrategy":"validation","validationCode":"// On the sender side, round-trip the operation against the shared ABI type before sending:\nuse my_app_abi::Operation; // the exact crate the contract compiles against\nlet bytes = bcs::to_bytes(&operation)?;\nbcs::from_bytes::<Operation>(&bytes)?; // if this fails locally, it would panic on-chain","typeGuard":"fn operation_round_trips(op: &Operation) -> bool {\n    bcs::to_bytes(op).ok().and_then(|b| bcs::from_bytes::<Operation>(&b).ok()).is_some()\n}","tryCatchPattern":"// The panic occurs inside the on-chain contract runtime; callers cannot catch it.\n// Guard at the boundary: only send operations built from the application's own ABI crate,\n// and verify application bytecode hashes match the published modules before calling.","preventionTips":["Generate operation bytes exclusively via the application's shared ABI crate; never hand-encode.","Publish contract and service bytecode as a matched pair from one build.","Treat any Operation type change as a new application: publish fresh instead of reusing IDs.","Add a CI test that BCS round-trips every Operation variant."],"tags":["linera-sdk","contract","bcs","abi-mismatch","deserialization","panic","rust"],"backgroundTag":"abi-version-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}