unionlabs/union · error
Expected selector 0x{:08x}, got 0x{:08x} (raw: {})
Error message
Expected selector 0x{:08x}, got 0x{:08x} (raw: {}) What it means
The final assertion failure of send_and_expect_revert: a selector-shaped hex was extracted and its first 4 bytes parsed as u32, but actual != expected_revert_code. The tx reverted with a different error than the test asserted. The message helpfully prints both selectors and the full raw hex so you can identify which contract error actually fired.
Source
Thrown at tools/union-test/src/lib.rs:1061
let hex_full = caps
.get(1)
.map(|m| m.as_str())
.ok_or_else(|| anyhow!("regex matched but capture group missing"))?;
let hexdigits = hex_full.trim_start_matches("0x");
if hexdigits.len() < 8 {
anyhow::bail!("rawValue too short for a selector: {}", hex_full);
}
let selector_hex = &hexdigits[..8]; // first 4 bytes
let actual = u32::from_str_radix(selector_hex, 16).with_context(|| {
format!("parsing revert selector `{selector_hex}` from `{hex_full}`")
})?;
if actual == expected_revert_code {
Ok(())
} else {
anyhow::bail!(
"Expected selector 0x{:08x}, got 0x{:08x} (raw: {})",
expected_revert_code,
actual,
hex_full
)
}
}
}
}
pub async fn send_and_recv_withdraw<Src: ChainEndpoint, Dst: ChainEndpoint>(
&self,
source_chain: &Src,
contract: Src::Contract,
msg: Src::Msg,
destination_chain: &Dst,
timeout: Duration,
signer: Src::ProviderType,View on GitHub (pinned to 031785bb6d)
Solutions
- Decode the actual selector in the message against the contract's error list (e.g. cast 4byte / selector tables) to learn which error fired, then fix the precondition so the intended branch trips.
- Recompute expected_revert_code from the current ABI: keccak256("ErrorName(type1,type2)")[0..4].
- If the actual error reveals a test-fixture problem (missing funds, channel not set up), fix the setup, not the constant.
- Prefer deriving the expected selector in code from the typed contract bindings rather than a hand-written u32.
Example fix
// before — hand-written selector for the old ABI const EXPECTED: u32 = 0x9e87c1a2; // after — derive it from current bindings use contract::Error; let expected = u32::from_be_bytes(Error::Unauthorized.selector());
Defensive patterns
Strategy: validation
Validate before calling
// assert intent before the call: derive expected from live bindings let expected = u32::from_be_bytes(MyError::InsufficientFunds.selector()); assert_eq!(expected, EXPECTED_REVERT_CODE, "fixture selector drifted from ABI");
Try / catch
match tw.send_and_expect_revert(&src, contract, msg, expected, &signer).await {
Err(e) if e.to_string().contains("Expected selector") => { /* decode actual selector, fix precondition */ panic!("{e:#}") }
other => other?,
} Prevention
- Compute expected selectors from bindings each run instead of hard-coding.
- Name the expected error in test comments and assert precondition state before the send.
When it happens
Trigger: The msg hits a different require/error branch than intended (e.g. INSUFFICIENT_FUNDS instead of UNAUTHORIZED because fixture state differs), expected_revert_code computed from the wrong error signature or a stale ABI, or big-endian/endianness or digit-order mistake when hand-writing the expected constant.
Common situations: Contract error enum reordered or renamed after an upgrade while the test constant stayed; shared chain state changed which guard trips first; expected selector written with padded wrong width (0x{:08x} formatting hides missing digits).
Related errors
- Expected revert 0x{:08x}, but transaction succeeded
- rawValue too short for a selector: {}
- Transaction reverted but no rawValue hex found: {}
- not implemented
- not implemented
AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16).
Data as JSON: /api/errors/137ec2e883151173.
Report an issue: GitHub.