linera-io/linera-protocol · error
expected exactly 2 topics (signature + indexed depositor), g
Error message
expected exactly 2 topics (signature + indexed depositor), got {} What it means
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.
Source
Thrown at linera-bridge/src/proof/mod.rs:435
/// Parses a `DepositInitiated` event from a receipt log.
///
/// Verifies that `topic[0]` matches the event signature, that the log was emitted by
/// the `expected_emitter` (bridge contract address), and ABI-decodes the data fields.
/// The `depositor` field is indexed (stored in `topics[1]`); all other parameters are
/// non-indexed and encoded in the log data.
pub fn parse_deposit_event(log: &ReceiptLog, expected_emitter: Address) -> Result<DepositEvent> {
ensure!(
log.address == expected_emitter,
"log emitter {:?} does not match expected bridge contract {:?}",
log.address,
expected_emitter
);
ensure!(
log.topics.first() == Some(&deposit_event_signature()),
"event topic does not match DepositInitiated signature"
);
ensure!(
log.topics.len() == 2,
"expected exactly 2 topics (signature + indexed depositor), got {}",
log.topics.len()
);
ensure!(
log.data.len() == 224,
"expected 224 bytes of event data (7 x 32), got {}",
log.data.len()
);
// Indexed `depositor` is in topics[1], left-padded to 32 bytes.
let depositor_topic = log.topics[1];
ensure!(
depositor_topic.as_slice()[..12] == [0u8; 12],
"invalid ABI encoding: depositor topic padding bytes (0..12) must be zero"
);
let depositor = Address::from_slice(&depositor_topic.as_slice()[12..32]);
View on GitHub (pinned to 6c226ddcb3)
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.
Example fix
// before
let event = parse_deposit_event(&log, bridge_address)?;
// after
if log.topics.len() != 2 {
anyhow::bail!("skipping log: expected signature + indexed depositor, got {} topics", log.topics.len());
}
let event = parse_deposit_event(&log, bridge_address)?; Defensive patterns
Strategy: validation
Validate before calling
use linera_bridge::proof::{deposit_event_signature, find_deposit_log_indices};
fn is_wellformed_deposit_topic_layout(log: &ReceiptLog) -> bool {
log.topics.first() == Some(&deposit_event_signature()) && log.topics.len() == 2
}
// before parsing:
let candidates = find_deposit_log_indices(&logs);
for i in candidates {
if !is_wellformed_deposit_topic_layout(&logs[i as usize]) { continue; }
if let Ok(event) = parse_deposit_event(&logs[i as usize], bridge_addr) { /* ... */ }
} Try / catch
match parse_deposit_event(&log, bridge_addr) {
Ok(event) => { /* ... */ }
Err(err) if err.to_string().contains("expected exactly 2 topics") => {
tracing::warn!(topics = log.topics.len(), "log has unexpected topic layout; skipping");
}
Err(err) => return Err(err),
} Prevention
- 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.
When it happens
Trigger: 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.
Common situations: 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.
Related errors
- expected 224 bytes of event data (7 x 32), got {}
- invalid ABI encoding: depositor topic padding bytes (0..12)
- invalid ABI encoding: address padding bytes (128..140) must
- topics must be an RLP list
- log data must be a byte string, not a list
AI-assisted analysis of linera-io/linera-protocol@6c226ddcb3 (2026-08-22).
Data as JSON: /api/errors/7e0cb0ee7a7e0e67.
Report an issue: GitHub.