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

  1. 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.
  2. Recompute expected_revert_code from the current ABI: keccak256("ErrorName(type1,type2)")[0..4].
  3. If the actual error reveals a test-fixture problem (missing funds, channel not set up), fix the setup, not the constant.
  4. 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

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


AI-assisted analysis of unionlabs/union@031785bb6d (2026-08-16). Data as JSON: /api/errors/137ec2e883151173. Report an issue: GitHub.