{"record":{"id":"7e0cb0ee7a7e0e67","repo":"linera-io/linera-protocol","slug":"expected-exactly-2-topics-signature-indexed-dep","errorCode":null,"errorMessage":"expected exactly 2 topics (signature + indexed depositor), got {}","messagePattern":"expected exactly 2 topics \\(signature \\+ indexed depositor\\), got (.+?)","errorType":"validation","errorClass":null,"httpStatus":null,"severity":"error","filePath":"linera-bridge/src/proof/mod.rs","lineNumber":435,"sourceCode":"\n/// Parses a `DepositInitiated` event from a receipt log.\n///\n/// Verifies that `topic[0]` matches the event signature, that the log was emitted by\n/// the `expected_emitter` (bridge contract address), and ABI-decodes the data fields.\n/// The `depositor` field is indexed (stored in `topics[1]`); all other parameters are\n/// non-indexed and encoded in the log data.\npub fn parse_deposit_event(log: &ReceiptLog, expected_emitter: Address) -> Result<DepositEvent> {\n    ensure!(\n        log.address == expected_emitter,\n        \"log emitter {:?} does not match expected bridge contract {:?}\",\n        log.address,\n        expected_emitter\n    );\n    ensure!(\n        log.topics.first() == Some(&deposit_event_signature()),\n        \"event topic does not match DepositInitiated signature\"\n    );\n    ensure!(\n        log.topics.len() == 2,\n        \"expected exactly 2 topics (signature + indexed depositor), got {}\",\n        log.topics.len()\n    );\n    ensure!(\n        log.data.len() == 224,\n        \"expected 224 bytes of event data (7 x 32), got {}\",\n        log.data.len()\n    );\n\n    // Indexed `depositor` is in topics[1], left-padded to 32 bytes.\n    let depositor_topic = log.topics[1];\n    ensure!(\n        depositor_topic.as_slice()[..12] == [0u8; 12],\n        \"invalid ABI encoding: depositor topic padding bytes (0..12) must be zero\"\n    );\n    let depositor = Address::from_slice(&depositor_topic.as_slice()[12..32]);\n","sourceCodeStart":417,"sourceCodeEnd":453,"githubUrl":"https://github.com/linera-io/linera-protocol/blob/6c226ddcb332ef55118dc8d0aafbd093d5420899/linera-bridge/src/proof/mod.rs#L417-L453","documentation":"parse_deposit_event ABI-decodes an EVM DepositInitiated log and requires exactly two topics: topic[0] is the Keccak event-signature hash and topic[1] is the single indexed `depositor` address. The check at linera-bridge/src/proof/mod.rs:435 fails when log.topics.len() != 2, meaning the log's indexed-parameter layout does not match the event the bridge contract is expected to emit. It fires only after the emitter address and topic[0] signature already matched, so it almost always indicates an ABI mismatch rather than a random foreign log.","triggerScenarios":"Calling parse_deposit_event with a log whose emitter and topic[0] match but which carries 1 topic (no indexed depositor) or 3+ topics (extra indexed params). This happens when the bridge contract is redeployed with a DepositInitiated(event) variant that indexes additional parameters, when a test fixture builds topics manually and omits the depositor topic, or when evm_scan_iteration feeds a log from a different contract version.","commonSituations":"Upgrading the bridge contract and adding an indexed field without updating the Rust parser; pointing the scanner at the wrong contract address that happens to reuse the same event signature; hand-crafted test logs in test_deposit_proof_generation/test_parse_deposit_event_valid that forget the depositor topic; forking the contract with modified event layout while reusing the original event signature.","solutions":["Inspect the deployed contract's ABI: confirm DepositInitiated has exactly one indexed parameter (address depositor) plus 7 non-indexed words.","If the contract legitimately changed, update the parser's topic-count expectation and the field offsets together, and regenerate deposit_event_signature() from the new ABI.","Pre-filter logs with find_deposit_log_indices(logs) so only logs with the exact signature reach parse_deposit_event.","If the emitter check passed but layout differs, you are likely decoding a different contract version: verify the deployed bytecode/ABI hash matches what the parser was written for."],"exampleFix":"// before\nlet event = parse_deposit_event(&log, bridge_address)?;\n\n// after\nif log.topics.len() != 2 {\n    anyhow::bail!(\"skipping log: expected signature + indexed depositor, got {} topics\", log.topics.len());\n}\nlet event = parse_deposit_event(&log, bridge_address)?;","handlingStrategy":"validation","validationCode":"use linera_bridge::proof::{deposit_event_signature, find_deposit_log_indices};\n\nfn is_wellformed_deposit_topic_layout(log: &ReceiptLog) -> bool {\n    log.topics.first() == Some(&deposit_event_signature()) && log.topics.len() == 2\n}\n\n// before parsing:\nlet candidates = find_deposit_log_indices(&logs);\nfor i in candidates {\n    if !is_wellformed_deposit_topic_layout(&logs[i as usize]) { continue; }\n    if let Ok(event) = parse_deposit_event(&logs[i as usize], bridge_addr) { /* ... */ }\n}","typeGuard":null,"tryCatchPattern":"match parse_deposit_event(&log, bridge_addr) {\n    Ok(event) => { /* ... */ }\n    Err(err) if err.to_string().contains(\"expected exactly 2 topics\") => {\n        tracing::warn!(topics = log.topics.len(), \"log has unexpected topic layout; skipping\");\n    }\n    Err(err) => return Err(err),\n}","preventionTips":["Pin the scanner to a verified bridge-contract ABI; re-verify the ABI hash on deploy.","Pre-filter logs with find_deposit_log_indices before attempting full parses.","Keep contract deployment and parser version locked and deployed together.","In tests, generate logs from the ABI encoding, never hand-built topics."],"tags":["ethereum","evm","abi","event-logs","rust","linera-bridge"],"backgroundTag":"evm-event-abi-mismatch","analyzedSha":"6c226ddcb332ef55118dc8d0aafbd093d5420899","analyzedAt":"2026-08-22T22:49:09.787Z","schemaVersion":2},"datasetVersion":"2026-08-23T01:17:44.959Z"}