#[cfg(test)]
use crate::encoders::{self, SENTINEL_SELF};
use alloy::primitives::{Address, U256};
use degenbot_abi::abi_types::AbiValue;
use degenbot_abi::encoder::encode_rust;
pub const NATIVE_CURRENCY_ADDRESS: Address = Address::ZERO;
pub const EXECUTE_SELECTOR: [u8; 4] = [0xab, 0x58, 0x98, 0xe8];
#[derive(Clone, Debug)]
pub enum HopInfo {
V2(V2HopInfo),
V3(V3HopInfo),
V4(V4HopInfo),
}
#[derive(Clone, Debug)]
pub struct V2HopInfo {
pub pool_address: Address,
pub token0_address: Address,
pub token1_address: Address,
pub fee: u16,
pub zfo: bool,
}
#[derive(Clone, Debug)]
pub struct V3HopInfo {
pub pool_address: Address,
pub token0_address: Address,
pub token1_address: Address,
pub fee: u32,
pub zfo: bool,
}
#[derive(Clone, Debug)]
pub struct V4HopInfo {
pub pool_manager_address: Address,
pub pool_id_hex: String,
pub currency0_address: Address,
pub currency1_address: Address,
pub fee: u32,
pub tick_spacing: i32,
pub hook_address: Address,
pub zfo: bool,
}
#[derive(Clone, Debug)]
pub struct PathInfo {
pub hops: Vec<HopInfo>,
}
impl PathInfo {
#[must_use]
pub fn new(hops: Vec<HopInfo>) -> Self {
Self { hops }
}
}
#[must_use]
pub fn v4_input_is_native(hop: &V4HopInfo) -> bool {
let input_currency = if hop.zfo {
hop.currency0_address
} else {
hop.currency1_address
};
input_currency == NATIVE_CURRENCY_ADDRESS
}
#[must_use]
pub fn v4_output_is_native(hop: &V4HopInfo) -> bool {
let output_currency = if hop.zfo {
hop.currency1_address
} else {
hop.currency0_address
};
output_currency == NATIVE_CURRENCY_ADDRESS
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CurrencyBridge {
None,
Wrap,
Unwrap,
}
impl CurrencyBridge {
#[must_use]
pub const fn needs_bridge(self) -> bool {
!matches!(self, Self::None)
}
#[must_use]
pub fn at_boundary(output_currency_a: Address, input_currency_b: Address) -> Self {
let a_native = output_currency_a == NATIVE_CURRENCY_ADDRESS;
let b_native = input_currency_b == NATIVE_CURRENCY_ADDRESS;
match (a_native, b_native) {
(true, false) => Self::Wrap,
(false, true) => Self::Unwrap,
_ => Self::None,
}
}
#[must_use]
pub const fn bridge_indices(self, weth_idx: u8, native_idx: u8) -> (u8, u8) {
match self {
Self::None => (0, 0), Self::Wrap => (native_idx, weth_idx),
Self::Unwrap => (weth_idx, native_idx),
}
}
}
#[cfg(test)]
pub(crate) fn emit_currency_bridge(
inner: &mut Vec<u8>,
bridge: CurrencyBridge,
currency_idx: u8,
amount: u128,
) -> Option<()> {
match bridge {
CurrencyBridge::None => {}
CurrencyBridge::Wrap => {
inner.extend_from_slice(
&encoders::enc_v4_take_compact(currency_idx, SENTINEL_SELF, amount).ok()?,
);
inner.extend_from_slice(&encoders::enc_weth_deposit(U256::from(amount)));
}
CurrencyBridge::Unwrap => {
inner.extend_from_slice(
&encoders::enc_v4_take_compact(currency_idx, SENTINEL_SELF, amount).ok()?,
);
inner.extend_from_slice(&encoders::enc_weth_withdraw(U256::from(amount)));
}
}
Some(())
}
#[derive(Clone, Copy, Debug, Default)]
pub struct EncodeOptions {
pub erc6909_profit: bool,
pub use_v4_batch: bool,
pub funding: crate::grammar_ledger::FundingSource,
pub capture: crate::grammar_ledger::ProfitCapture,
pub bribe: crate::grammar_ledger::Bribe,
}
#[must_use]
pub fn resolve_axes(
opts: EncodeOptions,
) -> (
crate::grammar_ledger::FundingSource,
crate::grammar_ledger::ProfitCapture,
crate::grammar_ledger::Bribe,
) {
let capture = if opts.erc6909_profit {
crate::grammar_ledger::ProfitCapture::Erc6909
} else {
opts.capture
};
(opts.funding, capture, opts.bribe)
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub struct EncodeContext {
pub executor: Address,
pub pool_manager: Address,
pub weth: Address,
}
impl EncodeContext {
#[must_use]
pub fn new(executor: Address, pool_manager: Address, weth: Address) -> Self {
Self {
executor,
pool_manager,
weth,
}
}
}
#[derive(Clone, Debug)]
pub struct EncodeRequest {
pub path: PathInfo,
pub optimal_input: u128,
pub hop_outputs: Vec<u128>,
pub consumed_inputs: Vec<u128>,
pub opts: EncodeOptions,
}
impl EncodeRequest {
#[must_use]
pub fn new(
path: PathInfo,
optimal_input: u128,
hop_outputs: Vec<u128>,
consumed_inputs: Vec<u128>,
opts: EncodeOptions,
) -> Self {
let n = path.hops.len();
assert_eq!(
hop_outputs.len(),
n,
"EncodeRequest: hop_outputs has {} entries for a {}-hop path",
hop_outputs.len(),
n
);
assert_eq!(
consumed_inputs.len(),
n,
"EncodeRequest: consumed_inputs has {} entries for a {}-hop path",
consumed_inputs.len(),
n
);
Self {
path,
optimal_input,
hop_outputs,
consumed_inputs,
opts,
}
}
}
#[derive(Clone, Copy)]
pub struct ComposerInputs<'a> {
pub executor_address: Address,
pub pool_manager_address: Address,
pub weth_address: Address,
pub optimal_input: u128,
pub hop_outputs: &'a [u128],
pub consumed_inputs: &'a [u128],
pub opts: EncodeOptions,
}
#[must_use]
pub fn encode_cmd_stream(ctx: &EncodeContext, req: &EncodeRequest) -> Option<Vec<u8>> {
let num_hops = req.path.hops.len();
let inputs = ComposerInputs {
executor_address: ctx.executor,
pool_manager_address: ctx.pool_manager,
weth_address: ctx.weth,
optimal_input: req.optimal_input,
hop_outputs: &req.hop_outputs,
consumed_inputs: &req.consumed_inputs,
opts: req.opts,
};
if num_hops >= 2 && req.path.hops.iter().all(|h| matches!(h, HopInfo::V2(_))) {
crate::grammar_shape::derive_all_v2(&req.path, &inputs)
} else {
crate::grammar_shape::derive_shape(&req.path, &inputs)
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct EncodedCall {
pub to: Address,
pub data: Vec<u8>,
pub value: U256,
}
pub fn config_for_options(
opts: EncodeOptions,
expected_value: U256,
) -> Result<U256, crate::config::ConfigError> {
let _ = expected_value; let (_, capture, bribe) = resolve_axes(opts);
let check_mode = match capture {
crate::grammar_ledger::ProfitCapture::Erc6909 => 2u8,
crate::grammar_ledger::ProfitCapture::SweepToAddress => 3u8,
_ => 1u8,
};
let (bribe_bips, bribe_recipient_idx) = match bribe {
crate::grammar_ledger::Bribe::None => (0u16, 0u8),
crate::grammar_ledger::Bribe::Some {
bips,
recipient_idx,
} => (bips, recipient_idx),
};
crate::config::pack_config(check_mode, U256::ZERO, bribe_bips, bribe_recipient_idx)
}
pub fn encode_execute_call(
executor_address: Address,
commands: &[u8],
config: U256,
) -> Result<EncodedCall, degenbot_core::errors::AbiDecodeError> {
let values = [
AbiValue::Bytes(commands.to_vec()),
AbiValue::Uint(config, 256),
];
let encoded = encode_rust(&["bytes", "uint256"], &values)?;
let mut data = Vec::with_capacity(4 + encoded.len());
data.extend_from_slice(&EXECUTE_SELECTOR);
data.extend_from_slice(&encoded);
Ok(EncodedCall {
to: executor_address,
data,
value: U256::ZERO,
})
}
#[doc(hidden)]
#[must_use]
#[expect(clippy::too_many_arguments)] pub fn encode_cmd_3_hop(
path_info: &PathInfo,
optimal_input: u128,
hop_outputs: &[u128],
consumed_inputs: &[u128],
executor_address: Address,
pool_manager_address: Address,
weth_address: Address,
opts: EncodeOptions,
) -> Option<Vec<u8>> {
let inputs = ComposerInputs {
executor_address,
pool_manager_address,
weth_address,
optimal_input,
hop_outputs,
consumed_inputs,
opts,
};
crate::grammar_shape::derive_shape(path_info, &inputs)
}
#[cfg(test)]
#[expect(clippy::unwrap_used, clippy::expect_used)]
mod tests {
use super::*;
use crate::encoders::{self, AddressTable, SENTINEL_NATIVE, SENTINEL_SELF, SENTINEL_WETH};
use alloy::primitives::address;
#[test]
fn currency_bridge_both_native_is_none() {
let b = CurrencyBridge::at_boundary(NATIVE_CURRENCY_ADDRESS, NATIVE_CURRENCY_ADDRESS);
assert_eq!(b, CurrencyBridge::None);
assert!(!b.needs_bridge());
}
#[test]
fn currency_bridge_both_weth_is_none() {
let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let b = CurrencyBridge::at_boundary(weth, weth);
assert_eq!(b, CurrencyBridge::None);
}
#[test]
fn currency_bridge_native_to_weth_is_wrap() {
let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let b = CurrencyBridge::at_boundary(NATIVE_CURRENCY_ADDRESS, weth);
assert_eq!(b, CurrencyBridge::Wrap);
assert!(b.needs_bridge());
}
#[test]
fn currency_bridge_weth_to_native_is_unwrap() {
let weth = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
let b = CurrencyBridge::at_boundary(weth, NATIVE_CURRENCY_ADDRESS);
assert_eq!(b, CurrencyBridge::Unwrap);
assert!(b.needs_bridge());
}
#[test]
fn currency_bridge_native_to_erc20_is_wrap() {
let usdc = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let b = CurrencyBridge::at_boundary(NATIVE_CURRENCY_ADDRESS, usdc);
assert_eq!(b, CurrencyBridge::Wrap);
}
#[test]
fn currency_bridge_erc20_to_native_is_unwrap() {
let usdc = address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48");
let b = CurrencyBridge::at_boundary(usdc, NATIVE_CURRENCY_ADDRESS);
assert_eq!(b, CurrencyBridge::Unwrap);
}
#[test]
fn emit_currency_bridge_none_emits_nothing() {
let mut inner = Vec::new();
let result = emit_currency_bridge(&mut inner, CurrencyBridge::None, 0xFE, 1000);
assert_eq!(result, Some(()));
assert!(inner.is_empty(), "None bridge must emit zero bytes");
}
#[test]
fn emit_currency_bridge_wrap_emits_take_plus_deposit() {
let mut inner = Vec::new();
let native_idx = 0xFF;
let amount = 1_000_000_000_000_000_000u128;
emit_currency_bridge(&mut inner, CurrencyBridge::Wrap, native_idx, amount)
.expect("Wrap bridge encodes");
assert_eq!(inner.len(), 15 + 33);
assert_eq!(inner[0], 0x52); assert_eq!(inner[1], native_idx);
assert_eq!(inner[2], SENTINEL_SELF);
assert_eq!(inner[15], 0x12); }
#[test]
fn currency_bridge_indices_wrap_takes_native_settles_weth() {
let (take, settle) = CurrencyBridge::Wrap.bridge_indices(SENTINEL_WETH, SENTINEL_NATIVE);
assert_eq!(take, SENTINEL_NATIVE, "Wrap takes native out of the PM");
assert_eq!(
settle, SENTINEL_WETH,
"Wrap settles WETH (the swap consumed WETH)"
);
}
#[test]
fn currency_bridge_indices_unwrap_takes_weth_settles_native() {
let (take, settle) = CurrencyBridge::Unwrap.bridge_indices(SENTINEL_WETH, SENTINEL_NATIVE);
assert_eq!(take, SENTINEL_WETH, "Unwrap takes WETH out of the PM");
assert_eq!(
settle, SENTINEL_NATIVE,
"Unwrap settles native (the swap consumed native)"
);
}
#[test]
fn currency_bridge_indices_none_is_unused_but_well_defined() {
let (take, settle) = CurrencyBridge::None.bridge_indices(SENTINEL_WETH, SENTINEL_NATIVE);
assert_eq!((take, settle), (0, 0));
}
#[test]
fn emit_currency_bridge_unwrap_emits_take_plus_withdraw() {
let mut inner = Vec::new();
let weth_idx = SENTINEL_WETH;
let amount = 2_000_000_000_000_000_000u128;
emit_currency_bridge(&mut inner, CurrencyBridge::Unwrap, weth_idx, amount)
.expect("Unwrap bridge encodes");
assert_eq!(inner.len(), 15 + 33);
assert_eq!(inner[0], 0x52); assert_eq!(inner[15], 0x13); }
#[test]
#[expect(clippy::similar_names)] fn three_hop_v2_v4_v3_feeds_clamped_consumed_input_as_v4_swap_in() {
let forward = 2_000_000_000u128;
let clamped = 1_999_999_999u128; let rust = encode_cmd_3_hop(
&PathInfo::new(vec![
HopInfo::V2(V2HopInfo {
pool_address: address!("1111111111111111111111111111111111111111"),
token0_address: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
token1_address: address!("A0b86991c6218b36c1D19D4a2e9Eb0cE3606eB48"),
fee: 30,
zfo: true,
}),
HopInfo::V4(V4HopInfo {
pool_manager_address: address!("000000000004444c5dc75cB358380D2e3dE08A90"),
pool_id_hex:
"0x1111111111111111111111111111111111111111111111111111111111111111"
.to_string(),
currency0_address: address!("A0b86991c6218b36c1D19D4a2e9Eb0cE3606eB48"),
currency1_address: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
fee: 500,
tick_spacing: 10,
hook_address: address!("0000000000000000000000000000000000000000"),
zfo: true,
}),
HopInfo::V3(V3HopInfo {
pool_address: address!("6666666666666666666666666666666666666666"),
token0_address: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
token1_address: address!("A0b86991c6218b36c1D19D4a2e9Eb0cE3606eB48"),
fee: 3000,
zfo: true,
}),
]),
1_000_000_000_000_000_000u128,
&[forward, 2_001_000_000_000_000_000u128, 2_001_000_000u128],
&[1_000_000_000_000_000_000u128, clamped, 2_001_000_000u128],
address!("DeAd0000000000000000000000000000000000Be"),
address!("000000000004444c5dc75cB358380D2e3dE08A90"),
address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
EncodeOptions::default(),
)
.expect("V2-V4-V3 encodes");
let mut at = AddressTable::with_sentinels(
Some(address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2")),
Some(address!("DeAd0000000000000000000000000000000000Be")),
Some(address!("000000000004444c5dc75cB358380D2e3dE08A90")),
);
let pm_idx = at
.add(address!("000000000004444c5dc75cB358380D2e3dE08A90"))
.unwrap();
let forward_a_idx = at
.add(address!("A0b86991c6218b36c1D19D4a2e9Eb0cE3606eB48"))
.unwrap();
let forward_b_idx = at
.add(address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"))
.unwrap();
let executor_idx = SENTINEL_SELF;
let zero_idx = SENTINEL_NATIVE;
let v2a_idx = at
.add(address!("1111111111111111111111111111111111111111"))
.unwrap();
let v3c_idx = at
.add(address!("6666666666666666666666666666666666666666"))
.unwrap();
let c0_b_idx = at
.add(address!("A0b86991c6218b36c1D19D4a2e9Eb0cE3606eB48"))
.unwrap();
let c1_b_idx = at
.add(address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"))
.unwrap();
let mut v4_inner = Vec::new();
v4_inner.extend_from_slice(&encoders::enc_v4_sync(forward_a_idx));
v4_inner.extend_from_slice(&encoders::enc_v2_swap_calc(v2a_idx, true, pm_idx, 30));
v4_inner.extend_from_slice(&encoders::enc_v4_settle());
v4_inner.extend_from_slice(
&encoders::enc_v4_swap_compact(c0_b_idx, c1_b_idx, 500, 10, zero_idx, true, clamped)
.unwrap(),
);
v4_inner.extend_from_slice(
&encoders::enc_v4_take_compact(forward_b_idx, v3c_idx, 2_001_000_000).unwrap(),
);
v4_inner.extend_from_slice(&encoders::enc_v4_settle_delta(forward_a_idx));
let mut c_fwd = Vec::new();
c_fwd.extend_from_slice(
&encoders::enc_erc20_transfer(SENTINEL_WETH, v2a_idx, 1_000_000_000_000_000_000)
.unwrap(),
);
c_fwd.extend_from_slice(&encoders::enc_v4_unlock(&v4_inner).unwrap());
let commands = encoders::enc_v3_swap_compact(
v3c_idx,
true,
2_001_000_000,
executor_idx,
&c_fwd,
)
.unwrap();
let mut expected = encoders::enc_preamble(&at);
expected.extend_from_slice(&commands);
assert_eq!(
rust, expected,
"V4 swap-in must be the clamped consumed_inputs[1], not hop_outputs[0]"
);
}
#[test]
fn three_hop_v3_v4_v3_take_uses_consumed_inputs2_not_hop_outputs1() {
let optimal_input = 44_421_383_036_608_956u128;
let v4_predicted = 85_097_884u128;
let v4_actual = 85_097_881u128;
let take_from = |consumed2| {
let rust = encode_cmd_3_hop(
&PathInfo::new(vec![
HopInfo::V3(V3HopInfo {
pool_address: address!("E0554a476A092703abdB3Ef35c80e0D76d32939F"),
token0_address: address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
token1_address: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
fee: 100,
zfo: false,
}),
HopInfo::V4(V4HopInfo {
pool_manager_address: address!("000000000004444c5dc75cB358380D2e3dE08A90"),
pool_id_hex:
"0x8aa4e11cbdf30eedc92100f4c8a31ff748e201d44712cc8c90d189edaa8e4e47"
.to_string(),
currency0_address: address!("A0b86991c6218b36c1d19D4a2e9Eb0cE3606eB48"),
currency1_address: address!("dAC17F958D2ee523a2206206994597C13D831ec7"),
fee: 10,
tick_spacing: 1,
hook_address: address!("0000000000000000000000000000000000000000"),
zfo: true,
}),
HopInfo::V3(V3HopInfo {
pool_address: address!("c7bBeC68d12a0d1830360F8Ec58fA599bA1b0e9b"),
token0_address: address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
token1_address: address!("dAC17F958D2ee523a2206206994597C13D831ec7"),
fee: 100,
zfo: false,
}),
]),
optimal_input,
&[85_060_245, v4_predicted, 44_421_879_564_949_974],
&[optimal_input, 85_060_245, consumed2],
address!("DeAd0000000000000000000000000000000000Be"),
address!("000000000004444c5dc75cB358380D2e3dE08A90"),
address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2"),
EncodeOptions::default(),
)
.expect("V3-V4-V3 encodes");
let found = rust
.windows(15)
.find(|w| w[0] == 0x52 && w[3..].iter().any(|b| *b != 0))
.map(|w| {
let mut a = [0u8; 16];
a[4..].copy_from_slice(&w[3..15]); u128::from_be_bytes(a)
});
found
};
assert_eq!(take_from(v4_actual), Some(v4_actual));
assert_eq!(take_from(v4_actual), Some(v4_actual));
assert_ne!(take_from(v4_actual), Some(v4_predicted));
}
}
#[test]
fn resolve_axes_default_is_custody_no_bribe() {
let (funding, capture, bribe) = resolve_axes(EncodeOptions::default());
assert_eq!(funding, crate::grammar_ledger::FundingSource::InPathFlash);
assert_eq!(capture, crate::grammar_ledger::ProfitCapture::Custody);
assert_eq!(bribe, crate::grammar_ledger::Bribe::None);
}
#[test]
fn resolve_axes_legacy_erc6909_bool_forces_erc6909_capture() {
for capture in [
crate::grammar_ledger::ProfitCapture::Custody,
crate::grammar_ledger::ProfitCapture::Owner,
crate::grammar_ledger::ProfitCapture::Native,
] {
let opts = EncodeOptions {
erc6909_profit: true,
use_v4_batch: false,
capture,
..Default::default()
};
assert_eq!(
resolve_axes(opts).1,
crate::grammar_ledger::ProfitCapture::Erc6909,
"legacy erc6909_profit:true must force Erc6909 even with capture={capture:?}"
);
}
}
#[test]
fn resolve_axes_capture_field_honored_when_legacy_bool_false() {
for capture in [
crate::grammar_ledger::ProfitCapture::Custody,
crate::grammar_ledger::ProfitCapture::Owner,
crate::grammar_ledger::ProfitCapture::Native,
crate::grammar_ledger::ProfitCapture::Erc6909,
] {
let opts = EncodeOptions {
erc6909_profit: false,
use_v4_batch: false,
capture,
..Default::default()
};
assert_eq!(resolve_axes(opts).1, capture);
}
}
#[test]
fn resolve_axes_bribe_passes_through() {
let opts = EncodeOptions {
bribe: crate::grammar_ledger::Bribe::Some {
bips: 50,
recipient_idx: 0,
},
..Default::default()
};
assert_eq!(
resolve_axes(opts).2,
crate::grammar_ledger::Bribe::Some {
bips: 50,
recipient_idx: 0
}
);
}
#[test]
fn resolve_axes_funding_passes_through() {
for funding in [
crate::grammar_ledger::FundingSource::SelfFund,
crate::grammar_ledger::FundingSource::PmLedger,
crate::grammar_ledger::FundingSource::ExternalLender,
crate::grammar_ledger::FundingSource::Erc6909BurnToSettle,
] {
let opts = EncodeOptions {
funding,
..Default::default()
};
assert_eq!(resolve_axes(opts).0, funding);
}
}
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_default_is_check_mode_1() {
let cfg = config_for_options(EncodeOptions::default(), U256::ZERO).unwrap();
assert_eq!(
cfg & U256::from(255u64),
U256::from(1u64),
"default → check_mode=1"
);
assert_eq!(
(cfg >> 8) & U256::from(65535u64),
U256::ZERO,
"no bribe by default"
);
}
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_capture_erc6909_sets_check_mode_2() {
let opts = EncodeOptions {
capture: crate::grammar_ledger::ProfitCapture::Erc6909,
..Default::default()
};
let cfg = config_for_options(opts, U256::ZERO).unwrap();
assert_eq!(
cfg & U256::from(255u64),
U256::from(2u64),
"Erc6909 → check_mode=2"
);
}
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_capture_native_is_check_mode_1() {
let opts = EncodeOptions {
capture: crate::grammar_ledger::ProfitCapture::Native,
..Default::default()
};
let cfg = config_for_options(opts, U256::ZERO).unwrap();
assert_eq!(
cfg & U256::from(255u64),
U256::from(1u64),
"Native → check_mode=1 (assert active)"
);
}
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_legacy_erc6909_bool_forces_check_mode_2() {
let opts = EncodeOptions {
erc6909_profit: true,
capture: crate::grammar_ledger::ProfitCapture::Custody, ..Default::default()
};
let cfg = config_for_options(opts, U256::ZERO).unwrap();
assert_eq!(
cfg & U256::from(255u64),
U256::from(2u64),
"legacy bool forces check_mode=2"
);
}
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_bribe_packs_bips_and_recipient() {
let opts = EncodeOptions {
bribe: crate::grammar_ledger::Bribe::Some {
bips: 500,
recipient_idx: 3,
},
..Default::default()
};
let cfg = config_for_options(opts, U256::ZERO).unwrap();
assert_eq!((cfg >> 8) & U256::from(65535u64), U256::from(500u64));
assert_eq!((cfg >> 24) & U256::from(255u64), U256::from(3u64));
}
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_expected_value_is_ignored() {
let ev = U256::from(0xBEEFu64);
let cfg = config_for_options(EncodeOptions::default(), ev).unwrap();
assert_eq!(
cfg >> 32,
U256::ZERO,
"expected_value ignored by the builder"
);
}
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_combines_all_axes() {
let opts = EncodeOptions {
capture: crate::grammar_ledger::ProfitCapture::Erc6909,
bribe: crate::grammar_ledger::Bribe::Some {
bips: 500,
recipient_idx: 0,
},
..Default::default()
};
let cfg = config_for_options(opts, U256::from(1_000_000u64)).unwrap();
assert_eq!(cfg & U256::from(255u64), U256::from(2u64)); assert_eq!((cfg >> 8) & U256::from(65535u64), U256::from(500u64)); assert_eq!((cfg >> 24) & U256::from(255u64), U256::ZERO); assert_eq!(cfg >> 32, U256::ZERO); }
#[test]
#[expect(clippy::unwrap_used)] fn config_for_options_capture_sweep_to_address_sets_check_mode_3() {
let opts = EncodeOptions {
capture: crate::grammar_ledger::ProfitCapture::SweepToAddress,
..Default::default()
};
let cfg = config_for_options(opts, U256::ZERO).unwrap();
assert_eq!(
cfg & U256::from(255u64),
U256::from(3u64),
"SweepToAddress → check_mode=3 (SWEEP)"
);
}