use super::super::{fits_i128, mechanics, HopFacts, RepayMechanism, SeedDelivery, TerminalForm};
use crate::composers::{resolve_axes, ComposerInputs, CurrencyBridge, NATIVE_CURRENCY_ADDRESS};
use crate::encoders::{AddressTable, SENTINEL_NATIVE, SENTINEL_SELF, SENTINEL_WETH};
use crate::grammar_ledger::Prot;
use crate::grammar_plan::{Plan, PlanStep, V4BatchSwap};
use crate::grammar_shape::{
native_capture_declines, v4_bridge_steps, v4_scaffold_table, v4_terminal_capture_steps,
ProfitCapture,
};
#[expect(clippy::too_many_lines, clippy::needless_return)]
pub(crate) fn derive(
facts: &[HopFacts],
inputs: &ComposerInputs<'_>,
) -> Option<(Plan, AddressTable)> {
return if facts.len() == 3 && facts.iter().all(|f| matches!(f.prot, Prot::V2 | Prot::V3)) {
return rule_walk_v2v3(facts, inputs);
} else {
if facts.len() != 3 {
return None;
}
if facts[0].prot == Prot::V4
&& facts
.iter()
.all(|f| matches!(f.prot, Prot::V2 | Prot::V3 | Prot::V4))
{
return rule_walk_v4_led(facts, inputs);
} else if facts[0].prot != Prot::V4
&& (facts[1].prot == Prot::V4 || facts[2].prot == Prot::V4)
&& !(facts[0].prot == Prot::V3 && facts[1].prot == Prot::V4)
{
return rule_walk_v2v3_v4_mixed(facts, inputs);
} else if facts[0].prot == Prot::V3
&& facts[1].prot == Prot::V4
&& matches!(facts[2].prot, Prot::V2 | Prot::V4)
{
let (fa, fb, fc) = (&facts[0], &facts[1], &facts[2]);
let terminal_form = fc.terminal_form?;
let optimal_input = inputs.optimal_input;
let out_a = inputs.hop_outputs[0];
let out_b = inputs.hop_outputs[1];
let out_c = inputs.hop_outputs[2];
if inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
if !fits_i128(b_swap_in) {
return None;
}
let c_swap_in = match terminal_form {
TerminalForm::UnlockInternal => {
let c = *inputs.consumed_inputs.get(2)?;
if !fits_i128(c) {
return None;
}
Some(c)
}
TerminalForm::DirectHandoff => None,
};
let fwd_a = fa.out_currency;
let forward_b_cur = fb.out_currency;
let in_currency_b = fb.in_currency;
let weth = inputs.weth_address;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(inputs.pool_manager_address).ok()?;
let v3a = at.add(fa.pool_address).ok()?;
let v2c = match terminal_form {
TerminalForm::DirectHandoff => Some(at.add(fc.pool_address).ok()?),
TerminalForm::UnlockInternal => None,
};
let forward_a = at.add(fwd_a).ok()?;
let forward_b = match terminal_form {
TerminalForm::DirectHandoff => Some(at.add(forward_b_cur).ok()?),
TerminalForm::UnlockInternal => None,
};
let fee_b = fb.swap_fee;
let ts_b = fb.tick_spacing;
let c0_b = at.add(fb.currency0_address).ok()?;
let c1_b = at.add(fb.currency1_address).ok()?;
let (c0_c, c1_c) = match terminal_form {
TerminalForm::UnlockInternal => (
Some(at.add(fc.currency0_address).ok()?),
Some(at.add(fc.currency1_address).ok()?),
),
TerminalForm::DirectHandoff => (None, None),
};
let v4_unlock_inner_start: Vec<PlanStep> = vec![
PlanStep::V4Settle {
currency_addr: fwd_a,
amount: b_swap_in,
},
PlanStep::V4Swap {
c0_idx: c0_b,
c1_idx: c1_b,
fee: fee_b,
tick_spacing: ts_b,
hooks_idx: SENTINEL_NATIVE,
zfo: fb.zfo,
amount: b_swap_in,
in_currency: in_currency_b,
in_amount: b_swap_in,
out_currency: forward_b_cur,
out_amount: out_b,
},
];
let v4_inner: Plan = match terminal_form {
TerminalForm::DirectHandoff => {
let mut inner = v4_unlock_inner_start;
inner.push(PlanStep::V4TakeDelta {
currency_idx: forward_b.unwrap_or_default(),
currency_addr: forward_b_cur,
recipient_idx: v2c.unwrap_or_default(),
seeds_pool: Some(fc.pool_address),
});
inner.push(PlanStep::V4SettleAll);
inner
}
TerminalForm::UnlockInternal => {
let c_in = c_swap_in.unwrap_or_default();
let mut inner = v4_unlock_inner_start;
inner.push(PlanStep::V4Swap {
c0_idx: c0_c.unwrap_or_default(),
c1_idx: c1_c.unwrap_or_default(),
fee: fc.swap_fee,
tick_spacing: fc.tick_spacing,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_in,
in_currency: fc.in_currency,
in_amount: c_in,
out_currency: fc.out_currency,
out_amount: out_c,
});
inner.push(PlanStep::V4TakeDelta {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
recipient_idx: SENTINEL_SELF,
seeds_pool: None,
});
inner.push(PlanStep::V4SettleAll);
inner
}
};
let unlock_step = PlanStep::V4Unlock {
inner: v4_inner,
pool_manager_idx: pm_idx,
};
let repay_step = PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: v3a,
amount: optimal_input,
seeds_pool: None,
repays_flash: Some(fa.pool_address),
};
let cb: Plan = match terminal_form {
TerminalForm::DirectHandoff => {
let c_step =
mechanics::v2_swap(&mut at, fc, out_c, SENTINEL_SELF, None, false)?;
vec![unlock_step, c_step, repay_step]
}
TerminalForm::UnlockInternal => vec![repay_step, unlock_step],
};
let plan: Plan = vec![
PlanStep::SelfFund {
currency: weth,
amount: optimal_input,
},
PlanStep::V4Sync {
currency_idx: forward_a,
currency_addr: fwd_a,
},
mechanics::v3_flash(
&mut at,
fa,
out_a,
optimal_input,
false,
Some((pm_idx, None, false)),
cb,
)?,
];
Some((plan, at))
} else {
None
}
};
}
#[expect(clippy::too_many_lines)]
fn rule_walk_v2v3(facts: &[HopFacts], inputs: &ComposerInputs<'_>) -> Option<(Plan, AddressTable)> {
if facts.len() != 3 {
return None;
}
let prots = [facts[0].prot, facts[1].prot, facts[2].prot];
if !prots.iter().all(|p| matches!(p, Prot::V2 | Prot::V3)) {
return None;
}
let hops: [&HopFacts; 3] = [&facts[0], &facts[1], &facts[2]];
let optimal = inputs.optimal_input;
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal) {
return None;
}
let outs = [
inputs.hop_outputs[0],
inputs.hop_outputs[1],
inputs.hop_outputs[2],
];
let weth = inputs.weth_address;
let flash = [
hops[0].prot == Prot::V3,
hops[1].prot == Prot::V3,
hops[2].prot == Prot::V3
|| (hops[2].prot == Prot::V2 && hops[0].prot == Prot::V2 && hops[1].prot == Prot::V3),
];
let all_flash = flash[0] && flash[1] && flash[2];
let b_in = if flash[1] {
let v = *inputs.consumed_inputs.get(1)?;
if !fits_i128(v) {
return None;
}
v
} else {
0
};
let c_in = if flash[2] {
let v = *inputs.consumed_inputs.get(2)?;
if !fits_i128(v) {
return None;
}
v
} else {
0
};
let in_amounts = [optimal, b_in, c_in];
let mut at = AddressTable::with_sentinels(Some(weth), Some(inputs.executor_address), None);
let (a_idx, b_idx, c_idx) = stage_pools(&mut at, &hops)?;
let pool_idx = [a_idx, b_idx, c_idx];
let mut calc_chain: Vec<PlanStep> = Vec::new();
for i in 0..3 {
if flash[i] {
continue;
}
let (recipient_idx, recipient_pool_addr, recipient_repays) = if i < 2 {
(
pool_idx[i + 1],
Some(hops[i + 1].pool_address),
flash[i + 1],
)
} else {
(SENTINEL_SELF, None, false)
};
if i == 0 && flash[1] {
calc_chain.push(mechanics::v2_swap_direct(
&mut at,
hops[0],
outs[0],
hops[0].out_currency,
recipient_idx,
recipient_pool_addr,
recipient_repays,
)?);
} else {
calc_chain.push(mechanics::v2_swap(
&mut at,
hops[i],
outs[i],
recipient_idx,
recipient_pool_addr,
recipient_repays,
)?);
}
}
let v3_led = flash[0];
let innermost = (0..3).find(|i| flash[*i])?;
let mut inner_cb: Vec<PlanStep> = Vec::new();
if !v3_led {
inner_cb.push(PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: a_idx,
amount: optimal,
seeds_pool: Some(hops[0].pool_address),
repays_flash: None,
});
}
inner_cb.extend(calc_chain);
if v3_led && !all_flash {
inner_cb.push(PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: a_idx,
amount: optimal,
seeds_pool: None,
repays_flash: Some(hops[0].pool_address),
});
}
let flash_idx: Vec<usize> = (0..3).filter(|i| flash[*i]).collect();
let mut inner_step: Option<PlanStep> = None;
for &i in &flash_idx {
let cb = if i == innermost {
inner_cb.clone()
} else {
vec![inner_step.clone()?]
};
let step = if hops[i].prot == Prot::V3 {
let (recipient_idx, recipient_pool_addr, recipient_pool_repays) = if i < 2 {
(
pool_idx[i + 1],
Some(hops[i + 1].pool_address),
flash[i + 1],
)
} else {
(SENTINEL_SELF, None, false)
};
mechanics::v3_flash(
&mut at,
hops[i],
outs[i],
in_amounts[i],
all_flash && i == 0,
Some((recipient_idx, recipient_pool_addr, recipient_pool_repays)),
cb,
)?
} else {
mechanics::v2_flash(
&mut at,
hops[i],
outs[i],
hops[i].in_currency,
in_amounts[i],
cb,
)?
};
inner_step = Some(step);
}
let outer = inner_step?;
Some((
vec![
PlanStep::SelfFund {
currency: weth,
amount: optimal,
},
outer,
],
at,
))
}
#[expect(clippy::match_same_arms)]
fn stage_pools(at: &mut AddressTable, hops: &[&HopFacts; 3]) -> Option<(u8, u8, u8)> {
let (fa, fb, fc) = (hops[0], hops[1], hops[2]);
match [fa.prot, fb.prot, fc.prot] {
[Prot::V3, Prot::V3, Prot::V3] => {
let a = at.add(fa.pool_address).ok()?;
let b = at.add(fb.pool_address).ok()?;
let c = at.add(fc.pool_address).ok()?;
Some((a, b, c))
}
[Prot::V3, Prot::V3, Prot::V2] => {
let c = at.add(fc.pool_address).ok()?;
let a = at.add(fa.pool_address).ok()?;
let b = at.add(fb.pool_address).ok()?;
Some((a, b, c))
}
[Prot::V3, Prot::V2, Prot::V3] => {
let b = at.add(fb.pool_address).ok()?;
let a = at.add(fa.pool_address).ok()?;
let c = at.add(fc.pool_address).ok()?;
Some((a, b, c))
}
[Prot::V3, Prot::V2, Prot::V2] => {
let b = at.add(fb.pool_address).ok()?;
let c = at.add(fc.pool_address).ok()?;
let a = at.add(fa.pool_address).ok()?;
Some((a, b, c))
}
[Prot::V2, Prot::V2 | Prot::V3, Prot::V3] => {
let a = at.add(fa.pool_address).ok()?;
let b = at.add(fb.pool_address).ok()?;
let c = at.add(fc.pool_address).ok()?;
Some((a, b, c))
}
[Prot::V2, Prot::V3, Prot::V2] => {
let _ = at.add(fa.out_currency).ok()?;
let a = at.add(fa.pool_address).ok()?;
let c = at.add(fc.pool_address).ok()?;
let b = at.add(fb.pool_address).ok()?;
Some((a, b, c))
}
_ => None,
}
}
#[expect(clippy::too_many_lines, clippy::similar_names)]
fn rule_walk_v4_led(
facts: &[HopFacts],
inputs: &ComposerInputs<'_>,
) -> Option<(Plan, AddressTable)> {
if facts.len() != 3 {
return None;
}
if facts[0].prot != Prot::V4
|| !facts
.iter()
.all(|f| matches!(f.prot, Prot::V2 | Prot::V3 | Prot::V4))
{
return None;
}
let (fa, fb, fc) = (&facts[0], &facts[1], &facts[2]);
let optimal_input = inputs.optimal_input;
let out_a = inputs.hop_outputs[0];
let out_b = inputs.hop_outputs[1];
let out_c = inputs.hop_outputs[2];
let weth = inputs.weth_address;
let pm_idx_sentinel = inputs.pool_manager_address;
match [fb.prot, fc.prot] {
[Prot::V4, Prot::V4] => {
if out_a == 0 || out_b == 0 || out_c == 0 {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let a_swap_in = *inputs.consumed_inputs.first()?;
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(a_swap_in) || !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let mid_a_out = fa.out_currency;
let in_currency_a = fa.in_currency;
let mid_b_out = fb.out_currency;
let mid_b_in = fb.in_currency;
let output_c = fc.out_currency;
let mid_c_in = fc.in_currency;
let capture = resolve_axes(inputs.opts).1;
if native_capture_declines(capture, output_c, weth) {
return None;
}
let bridge_ab = CurrencyBridge::at_boundary(mid_a_out, mid_b_in);
let bridge_bc = CurrencyBridge::at_boundary(mid_b_out, mid_c_in);
let any_gap = bridge_ab.needs_bridge() || bridge_bc.needs_bridge();
let mut at = v4_scaffold_table(inputs);
let c0_a = at.add(fa.currency0_address).ok()?;
let c1_a = at.add(fa.currency1_address).ok()?;
let c0_b = at.add(fb.currency0_address).ok()?;
let c1_b = at.add(fb.currency1_address).ok()?;
let c0_c = at.add(fc.currency0_address).ok()?;
let c1_c = at.add(fc.currency1_address).ok()?;
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let fee_a = fa.swap_fee;
let ts_a = fa.tick_spacing;
let fee_b = fb.swap_fee;
let ts_b = fb.tick_spacing;
let fee_c = fc.swap_fee;
let ts_c = fc.tick_spacing;
let profit = out_c.saturating_sub(optimal_input);
let terminal_idx = if output_c == weth {
SENTINEL_WETH
} else if output_c == NATIVE_CURRENCY_ADDRESS {
SENTINEL_NATIVE
} else {
at.add(output_c).ok()?
};
let v4swap_a = PlanStep::V4Swap {
c0_idx: c0_a,
c1_idx: c1_a,
fee: fee_a,
tick_spacing: ts_a,
hooks_idx: SENTINEL_NATIVE,
zfo: fa.zfo,
amount: a_swap_in,
in_currency: in_currency_a,
in_amount: a_swap_in,
out_currency: mid_a_out,
out_amount: out_a,
};
let v4swap_b = PlanStep::V4Swap {
c0_idx: c0_b,
c1_idx: c1_b,
fee: fee_b,
tick_spacing: ts_b,
hooks_idx: SENTINEL_NATIVE,
zfo: fb.zfo,
amount: b_swap_in,
in_currency: mid_b_in,
in_amount: b_swap_in,
out_currency: mid_b_out,
out_amount: out_b,
};
let v4swap_c = PlanStep::V4Swap {
c0_idx: c0_c,
c1_idx: c1_c,
fee: fee_c,
tick_spacing: ts_c,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_swap_in,
in_currency: mid_c_in,
in_amount: c_swap_in,
out_currency: output_c,
out_amount: out_c,
};
let mut inner: Plan = if inputs.opts.use_v4_batch && !any_gap {
let open_weth = capture == ProfitCapture::Erc6909 && output_c == weth;
let mut steps = vec![PlanStep::V4Batch {
entries: vec![
V4BatchSwap {
c0_idx: c0_a,
c1_idx: c1_a,
fee: fee_a,
tick_spacing: ts_a,
hooks_idx: SENTINEL_NATIVE,
zfo: fa.zfo,
amount: a_swap_in,
in_currency: in_currency_a,
in_amount: a_swap_in,
out_currency: mid_a_out,
out_amount: out_a,
},
V4BatchSwap {
c0_idx: c0_b,
c1_idx: c1_b,
fee: fee_b,
tick_spacing: ts_b,
hooks_idx: SENTINEL_NATIVE,
zfo: fb.zfo,
amount: b_swap_in,
in_currency: mid_b_in,
in_amount: b_swap_in,
out_currency: mid_b_out,
out_amount: out_b,
},
V4BatchSwap {
c0_idx: c0_c,
c1_idx: c1_c,
fee: fee_c,
tick_spacing: ts_c,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_swap_in,
in_currency: mid_c_in,
in_amount: c_swap_in,
out_currency: output_c,
out_amount: out_c,
},
],
open_weth,
}];
if output_c != NATIVE_CURRENCY_ADDRESS && output_c != weth {
steps.push(PlanStep::V4TakeDelta {
currency_idx: terminal_idx,
currency_addr: output_c,
recipient_idx: SENTINEL_SELF,
seeds_pool: None,
});
}
steps
} else {
let mut steps = vec![v4swap_a];
if bridge_ab.needs_bridge() {
let (bridge_steps, settle_idx, settle_currency) =
v4_bridge_steps(bridge_ab, weth, out_a);
steps.extend(bridge_steps);
steps.push(v4swap_b);
steps.push(PlanStep::V4SettleDelta {
currency_idx: settle_idx,
currency_addr: settle_currency,
});
} else {
steps.push(v4swap_b);
}
if bridge_bc.needs_bridge() {
let (bridge_steps, settle_idx, settle_currency) =
v4_bridge_steps(bridge_bc, weth, out_b);
steps.extend(bridge_steps);
steps.push(v4swap_c);
steps.push(PlanStep::V4SettleDelta {
currency_idx: settle_idx,
currency_addr: settle_currency,
});
} else {
steps.push(v4swap_c);
}
steps
};
inner.append(&mut v4_terminal_capture_steps(
output_c,
terminal_idx,
capture,
inputs.opts.use_v4_batch,
any_gap,
profit,
weth,
));
inner.push(PlanStep::V4SettleAll);
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
}
[Prot::V2, Prot::V2] => {
if out_a == 0 || inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let forward_a_cur = fa.out_currency;
let in_currency_a = fa.in_currency;
if in_currency_a != weth {
return None;
}
let mut at = v4_scaffold_table(inputs);
let forward_a = at.add(forward_a_cur).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, optimal_input, out_a)?;
let v2b = at.add(fb.pool_address).ok()?;
let v2c = at.add(fc.pool_address).ok()?;
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let b_step = mechanics::v2_swap(&mut at, fb, out_b, v2c, Some(fc.pool_address), false)?;
let c_step = mechanics::v2_swap(&mut at, fc, out_c, SENTINEL_SELF, None, false)?;
let inner: Plan = vec![
v4swap_a,
PlanStep::V4TakeCompact {
currency_idx: forward_a,
currency_addr: forward_a_cur,
recipient_idx: v2b,
amount: out_a,
seeds_pool: Some(fb.pool_address),
repays_flash: None,
},
b_step,
c_step,
PlanStep::V4SettleDelta {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
},
];
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
}
[Prot::V2, Prot::V4] => {
if out_a == 0 || inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(c_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let b_forward_cur = fb.out_currency;
let mut at = v4_scaffold_table(inputs);
let forward_a = at.add(forward_a_cur).ok()?;
let _ = at.add(b_forward_cur).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, optimal_input, out_a)?;
let v4swap_c = mechanics::v4_swap(&mut at, fc, c_swap_in, out_c)?;
let v2b = at.add(fb.pool_address).ok()?;
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let b_step = mechanics::v2_swap(&mut at, fb, out_b, SENTINEL_SELF, None, false)?;
let inner: Plan = vec![
v4swap_a,
PlanStep::V4TakeCompact {
currency_idx: forward_a,
currency_addr: forward_a_cur,
recipient_idx: v2b,
amount: out_a,
seeds_pool: Some(fb.pool_address),
repays_flash: None,
},
b_step,
v4swap_c,
PlanStep::V4SettleAll,
];
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
}
[Prot::V3, Prot::V3] => {
if inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let v3b = at.add(fb.pool_address).ok()?;
let v3c = at.add(fc.pool_address).ok()?;
let _ = at.add(forward_a_cur).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, optimal_input, out_a)?;
let inner_take =
mechanics::v4_take_compact(&mut at, fa, v3b, out_a, Some(fb.pool_address))?;
let inner_b = mechanics::v3_flash(
&mut at,
fb,
out_b,
b_swap_in,
false,
Some((v3c, Some(fc.pool_address), true)),
vec![inner_take],
)?;
let outer_c = mechanics::v3_flash(
&mut at,
fc,
out_c,
c_swap_in,
false,
Some((SENTINEL_SELF, None, false)),
vec![inner_b],
)?;
let inner: Plan = vec![
v4swap_a,
outer_c,
PlanStep::V4SettleDelta {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
},
];
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
}
[Prot::V3, Prot::V4] => {
if inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let fwd_b = fb.out_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let v3b = at.add(fb.pool_address).ok()?;
let _ = at.add(forward_a_cur).ok()?;
let forward_b = at.add(fwd_b).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, optimal_input, out_a)?;
let v4swap_c = mechanics::v4_swap(&mut at, fc, c_swap_in, out_c)?;
let take = mechanics::v4_take_compact(&mut at, fa, v3b, out_a, Some(fb.pool_address))?;
let flash_b = mechanics::v3_flash(
&mut at,
fb,
out_b,
b_swap_in,
false,
Some((pm_idx, None, false)),
vec![take],
)?;
let inner: Plan = vec![
v4swap_a,
PlanStep::V4Sync {
currency_idx: forward_b,
currency_addr: fwd_b,
},
flash_b,
PlanStep::V4Settle {
currency_addr: fwd_b,
amount: out_b,
},
v4swap_c,
PlanStep::V4SettleAll,
];
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
}
[Prot::V4, Prot::V2] => {
if inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let a_swap_in = *inputs.consumed_inputs.first()?;
let b_swap_in = *inputs.consumed_inputs.get(1)?;
if !fits_i128(a_swap_in) || !fits_i128(b_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let in_currency_a = fa.in_currency;
let forward_b_cur = fb.out_currency;
let in_currency_b = fb.in_currency;
let mut at = v4_scaffold_table(inputs);
let forward_b = at.add(forward_b_cur).ok()?;
let v2c = at.add(fc.pool_address).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, a_swap_in, out_a)?;
let v4swap_b = mechanics::v4_swap(&mut at, fb, b_swap_in, out_b)?;
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let v2step = mechanics::v2_swap(&mut at, fc, out_c, SENTINEL_SELF, None, false)?;
let inner: Plan = vec![
v4swap_a,
v4swap_b,
PlanStep::V4TakeCompact {
currency_idx: forward_b,
currency_addr: forward_b_cur,
recipient_idx: v2c,
amount: out_b,
seeds_pool: Some(fc.pool_address),
repays_flash: None,
},
v2step,
PlanStep::V4SettleAll,
];
let _ = (forward_a_cur, in_currency_a, in_currency_b);
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
}
[Prot::V4, Prot::V3] => {
if inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let a_swap_in = *inputs.consumed_inputs.first()?;
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(a_swap_in) || !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let forward_b_cur = fb.out_currency;
let out_currency_c = fc.out_currency;
let fee_c = fc.swap_fee;
let mut at = v4_scaffold_table(inputs);
let forward_b = at.add(forward_b_cur).ok()?;
let v3c = at.add(fc.pool_address).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, a_swap_in, out_a)?;
let v4swap_b = mechanics::v4_swap(&mut at, fb, b_swap_in, out_b)?;
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let take = PlanStep::V4TakeCompact {
currency_idx: forward_b,
currency_addr: forward_b_cur,
recipient_idx: v3c,
amount: c_swap_in,
seeds_pool: None,
repays_flash: Some(fc.pool_address),
};
let flash_c = PlanStep::FlashSwap {
pool_idx: v3c,
pool_addr: fc.pool_address,
protocol: Prot::V3,
zfo: fc.zfo,
fee: fee_c,
out_currency: out_currency_c,
out_amount: out_c,
in_currency: forward_b_cur,
in_amount: c_swap_in,
recipient_idx: SENTINEL_SELF,
recipient_pool_addr: None,
recipient_pool_repays: false,
auto_repay: false,
callback: vec![take],
};
let inner: Plan = vec![v4swap_a, v4swap_b, flash_c, PlanStep::V4SettleAll];
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
}
[Prot::V2, Prot::V3] => {
if inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(c_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let in_currency_a = fa.in_currency;
if in_currency_a != weth {
return None;
}
let mut at = v4_scaffold_table(inputs);
let v3c = at.add(fc.pool_address).ok()?;
let forward_a = at.add(forward_a_cur).ok()?;
let _ = at.add(fb.out_currency).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, optimal_input, out_a)?;
let v2b = at.add(fb.pool_address).ok()?;
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let b_step = mechanics::v2_swap(&mut at, fb, out_b, v3c, Some(fc.pool_address), true)?;
let v4_inner: Plan = vec![
v4swap_a,
PlanStep::V4TakeCompact {
currency_idx: forward_a,
currency_addr: forward_a_cur,
recipient_idx: v2b,
amount: out_a,
seeds_pool: Some(fb.pool_address),
repays_flash: None,
},
b_step,
PlanStep::V4SettleDelta {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
},
];
let plan: Plan = vec![mechanics::v3_flash(
&mut at,
fc,
out_c,
c_swap_in,
false,
Some((SENTINEL_SELF, None, false)),
vec![PlanStep::V4Unlock {
inner: v4_inner,
pool_manager_idx: pm_idx,
}],
)?];
Some((plan, at))
}
[Prot::V3, Prot::V2] => {
if inputs.hop_outputs.contains(&0) {
return None;
}
if !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
if !fits_i128(b_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let in_currency_a = fa.in_currency;
let fwd_b = fb.out_currency;
let mut at = v4_scaffold_table(inputs);
let v3b = at.add(fb.pool_address).ok()?;
let forward_a = at.add(forward_a_cur).ok()?;
let _ = at.add(fwd_b).ok()?;
let v4swap_a = mechanics::v4_swap(&mut at, fa, optimal_input, out_a)?;
let v2c = at.add(fc.pool_address).ok()?;
let pm_idx = at.add(pm_idx_sentinel).ok()?;
let take = PlanStep::V4TakeCompact {
currency_idx: forward_a,
currency_addr: forward_a_cur,
recipient_idx: v3b,
amount: out_a,
seeds_pool: None,
repays_flash: Some(fb.pool_address),
};
let v2step = mechanics::v2_swap(&mut at, fc, out_c, SENTINEL_SELF, None, false)?;
let flash_b = PlanStep::FlashSwap {
pool_idx: v3b,
pool_addr: fb.pool_address,
protocol: Prot::V3,
zfo: fb.zfo,
fee: fb.swap_fee,
out_currency: fwd_b,
out_amount: out_b,
in_currency: forward_a_cur,
in_amount: b_swap_in,
recipient_idx: v2c,
recipient_pool_addr: Some(fc.pool_address),
recipient_pool_repays: false,
auto_repay: false,
callback: vec![take, v2step],
};
let inner: Plan = vec![
v4swap_a,
flash_b,
PlanStep::V4SettleDelta {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
},
];
let _ = in_currency_a;
let plan: Plan = vec![PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
}];
Some((plan, at))
} }
}
#[expect(clippy::too_many_lines, clippy::similar_names)]
fn rule_walk_v2v3_v4_mixed(
facts: &[HopFacts],
inputs: &ComposerInputs<'_>,
) -> Option<(Plan, AddressTable)> {
if facts.len() != 3 {
return None;
}
let (fa, fb, fc) = (&facts[0], &facts[1], &facts[2]);
if !matches!(fa.prot, Prot::V2 | Prot::V3) {
return None;
}
if fb.prot != Prot::V4 && fc.prot != Prot::V4 {
return None;
}
if fa.prot == Prot::V3 && fb.prot == Prot::V4 {
return None; }
let optimal_input = inputs.optimal_input;
let out_a = inputs.hop_outputs[0];
let out_b = inputs.hop_outputs[1];
let out_c = inputs.hop_outputs[2];
let weth = inputs.weth_address;
let pm = inputs.pool_manager_address;
match [fa.prot, fb.prot, fc.prot] {
[Prot::V2, Prot::V2, Prot::V4] => {
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal_input) {
return None;
}
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(c_swap_in) {
return None;
}
let fwd_b = fb.out_currency;
let output_c = fc.out_currency;
let in_currency_c = fc.in_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm).ok()?;
let v2a = at.add(fa.pool_address).ok()?;
let v2b = at.add(fb.pool_address).ok()?;
let fee_c = fc.swap_fee;
let ts_c = fc.tick_spacing;
let c0 = at.add(fc.currency0_address).ok()?;
let c1 = at.add(fc.currency1_address).ok()?;
let forward_b = at.add(fwd_b).ok()?;
let a_step = mechanics::v2_swap(&mut at, fa, out_a, v2b, Some(fb.pool_address), false)?;
let b_step = mechanics::v2_swap(&mut at, fb, c_swap_in, pm_idx, None, false)?;
let inner: Plan = vec![
PlanStep::V4Sync {
currency_idx: forward_b,
currency_addr: fwd_b,
},
PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: v2a,
amount: optimal_input,
seeds_pool: Some(fa.pool_address),
repays_flash: None,
},
a_step,
b_step,
PlanStep::V4Settle {
currency_addr: fwd_b,
amount: c_swap_in,
},
PlanStep::V4Swap {
c0_idx: c0,
c1_idx: c1,
fee: fee_c,
tick_spacing: ts_c,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_swap_in,
in_currency: in_currency_c,
in_amount: c_swap_in,
out_currency: output_c,
out_amount: out_c,
},
PlanStep::V4SettleAll,
];
let plan: Plan = vec![
PlanStep::SelfFund {
currency: weth,
amount: optimal_input,
},
PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
},
];
Some((plan, at))
}
[Prot::V2, Prot::V4, Prot::V4] => {
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let forward_b_cur = fb.out_currency;
let in_currency_b = fb.in_currency;
let output_c = fc.out_currency;
let in_currency_c = fc.in_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm).ok()?;
let forward_a = at.add(forward_a_cur).ok()?;
let fee_b = fb.swap_fee;
let ts_b = fb.tick_spacing;
let fee_c = fc.swap_fee;
let ts_c = fc.tick_spacing;
let c0_b = at.add(fb.currency0_address).ok()?;
let c1_b = at.add(fb.currency1_address).ok()?;
let c0_c = at.add(fc.currency0_address).ok()?;
let c1_c = at.add(fc.currency1_address).ok()?;
let v2a = at.add(fa.pool_address).ok()?;
let a_step = mechanics::v2_swap(&mut at, fa, b_swap_in, pm_idx, None, false)?;
let inner: Plan = vec![
PlanStep::V4Sync {
currency_idx: forward_a,
currency_addr: forward_a_cur,
},
PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: v2a,
amount: optimal_input,
seeds_pool: Some(fa.pool_address),
repays_flash: None,
},
a_step,
PlanStep::V4Settle {
currency_addr: forward_a_cur,
amount: b_swap_in,
},
PlanStep::V4Swap {
c0_idx: c0_b,
c1_idx: c1_b,
fee: fee_b,
tick_spacing: ts_b,
hooks_idx: SENTINEL_NATIVE,
zfo: fb.zfo,
amount: b_swap_in,
in_currency: in_currency_b,
in_amount: b_swap_in,
out_currency: forward_b_cur,
out_amount: out_b,
},
PlanStep::V4Swap {
c0_idx: c0_c,
c1_idx: c1_c,
fee: fee_c,
tick_spacing: ts_c,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_swap_in,
in_currency: in_currency_c,
in_amount: c_swap_in,
out_currency: output_c,
out_amount: out_c,
},
PlanStep::V4SettleAll,
];
let plan: Plan = vec![
PlanStep::SelfFund {
currency: weth,
amount: optimal_input,
},
PlanStep::V4Unlock {
inner,
pool_manager_idx: pm_idx,
},
];
Some((plan, at))
}
[Prot::V2, Prot::V3, Prot::V4] => {
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
if fa.seed_delivery != Some(SeedDelivery::V4TakeCompact) {
return None;
}
let fwd_b = fb.out_currency;
let fwd_a = fa.out_currency;
let output_c = fc.out_currency;
let in_currency_c = fc.in_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm).ok()?;
let v2a = at.add(fa.pool_address).ok()?;
let v3b = at.add(fb.pool_address).ok()?;
let fee_c = fc.swap_fee;
let ts_c = fc.tick_spacing;
let c0 = at.add(fc.currency0_address).ok()?;
let c1 = at.add(fc.currency1_address).ok()?;
let forward_b = at.add(fwd_b).ok()?;
let v4_inner: Plan = vec![
PlanStep::V4Settle {
currency_addr: fwd_b,
amount: c_swap_in,
},
PlanStep::V4Swap {
c0_idx: c0,
c1_idx: c1,
fee: fee_c,
tick_spacing: ts_c,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_swap_in,
in_currency: in_currency_c,
in_amount: c_swap_in,
out_currency: output_c,
out_amount: out_c,
},
PlanStep::V4TakeCompact {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
recipient_idx: v2a,
amount: optimal_input,
seeds_pool: Some(fa.pool_address),
repays_flash: None,
},
PlanStep::V4TakeCompact {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
recipient_idx: SENTINEL_SELF,
amount: out_c - optimal_input,
seeds_pool: None,
repays_flash: None,
},
PlanStep::V4Sync {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
},
PlanStep::V4SettleAll,
];
let cb: Plan = vec![
PlanStep::V4Unlock {
inner: v4_inner,
pool_manager_idx: pm_idx,
},
mechanics::v2_swap_direct(
&mut at,
fa,
out_a,
fwd_a,
v3b,
Some(fb.pool_address),
true,
)?,
];
let plan: Plan = vec![
PlanStep::V4Sync {
currency_idx: forward_b,
currency_addr: fwd_b,
},
mechanics::v3_flash(
&mut at,
fb,
out_b,
b_swap_in,
false,
Some((pm_idx, None, false)),
cb,
)?,
];
Some((plan, at))
}
[Prot::V3, Prot::V2, Prot::V4] => {
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
if fb.repay_mechanism != Some(RepayMechanism::AutoFromExecutor) {
return None;
}
let fwd_b = fb.out_currency;
let in_b = fb.in_currency;
let output_c = fc.out_currency;
let in_currency_c = fc.in_currency;
let mut at = v4_scaffold_table(inputs);
let v3a = at.add(fa.pool_address).ok()?;
let v2b = at.add(fb.pool_address).ok()?;
let forward_b = at.add(fwd_b).ok()?;
let fee_c = fc.swap_fee;
let ts_c = fc.tick_spacing;
let c0 = at.add(fc.currency0_address).ok()?;
let c1 = at.add(fc.currency1_address).ok()?;
let pm_idx = at.add(pm).ok()?;
let v4_inner: Plan = vec![
PlanStep::V4Swap {
c0_idx: c0,
c1_idx: c1,
fee: fee_c,
tick_spacing: ts_c,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_swap_in,
in_currency: in_currency_c,
in_amount: c_swap_in,
out_currency: output_c,
out_amount: out_c,
},
PlanStep::V4TakeCompact {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
recipient_idx: SENTINEL_SELF,
amount: out_c,
seeds_pool: None,
repays_flash: None,
},
PlanStep::V4SettleDelta {
currency_idx: forward_b,
currency_addr: fwd_b,
},
];
let v2_flash = PlanStep::FlashSwap {
pool_idx: v2b,
pool_addr: fb.pool_address,
protocol: Prot::V2,
zfo: fb.zfo,
fee: fb.swap_fee,
out_currency: fwd_b,
out_amount: out_b,
in_currency: in_b,
in_amount: b_swap_in,
recipient_idx: SENTINEL_SELF,
recipient_pool_addr: None,
recipient_pool_repays: false,
auto_repay: true,
callback: vec![PlanStep::V4Unlock {
inner: v4_inner,
pool_manager_idx: pm_idx,
}],
};
let a_cb: Plan = vec![
v2_flash,
PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: v3a,
amount: optimal_input,
seeds_pool: None,
repays_flash: Some(fa.pool_address),
},
];
let plan: Plan = vec![
PlanStep::SelfFund {
currency: weth,
amount: optimal_input,
},
mechanics::v3_flash(
&mut at,
fa,
out_a,
optimal_input,
false,
Some((v2b, None, false)),
a_cb,
)?,
];
Some((plan, at))
}
[Prot::V3, Prot::V3, Prot::V4] => {
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let fwd_b = fb.out_currency;
let output_c = fc.out_currency;
let in_currency_c = fc.in_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm).ok()?;
let v3a = at.add(fa.pool_address).ok()?;
let v3b = at.add(fb.pool_address).ok()?;
let forward_b = at.add(fwd_b).ok()?;
let fee_c = fc.swap_fee;
let ts_c = fc.tick_spacing;
let c0 = at.add(fc.currency0_address).ok()?;
let c1 = at.add(fc.currency1_address).ok()?;
let v4_inner: Plan = vec![
PlanStep::V4Settle {
currency_addr: fwd_b,
amount: c_swap_in,
},
PlanStep::V4Swap {
c0_idx: c0,
c1_idx: c1,
fee: fee_c,
tick_spacing: ts_c,
hooks_idx: SENTINEL_NATIVE,
zfo: fc.zfo,
amount: c_swap_in,
in_currency: in_currency_c,
in_amount: c_swap_in,
out_currency: output_c,
out_amount: out_c,
},
PlanStep::V4TakeCompact {
currency_idx: SENTINEL_WETH,
currency_addr: weth,
recipient_idx: v3a,
amount: optimal_input,
seeds_pool: None,
repays_flash: Some(fa.pool_address),
},
PlanStep::V4SettleAll,
];
let unlock = PlanStep::V4Unlock {
inner: v4_inner,
pool_manager_idx: pm_idx,
};
let inner_a = mechanics::v3_flash(
&mut at,
fa,
out_a,
optimal_input,
false,
Some((v3b, Some(fb.pool_address), true)),
vec![unlock],
)?;
let plan: Plan = vec![
PlanStep::V4Sync {
currency_idx: forward_b,
currency_addr: fwd_b,
},
mechanics::v3_flash(
&mut at,
fb,
out_b,
b_swap_in,
false,
Some((pm_idx, None, false)),
vec![inner_a],
)?,
];
Some((plan, at))
}
[Prot::V2, Prot::V4, Prot::V2] => {
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal_input) {
return None;
}
let b_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(b_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let forward_b_cur = fb.out_currency;
let in_currency_b = fb.in_currency;
let in_c = fc.in_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm).ok()?;
let forward_a = at.add(forward_a_cur).ok()?;
let forward_b = at.add(forward_b_cur).ok()?;
let v2a = at.add(fa.pool_address).ok()?;
let v2c = at.add(fc.pool_address).ok()?;
let fee_b = fb.swap_fee;
let ts_b = fb.tick_spacing;
let c0_b = at.add(fb.currency0_address).ok()?;
let c1_b = at.add(fb.currency1_address).ok()?;
let a_step = mechanics::v2_swap(&mut at, fa, b_swap_in, pm_idx, None, false)?;
let v4_inner: Plan = vec![
PlanStep::V4Sync {
currency_idx: forward_a,
currency_addr: forward_a_cur,
},
a_step,
PlanStep::V4Settle {
currency_addr: forward_a_cur,
amount: b_swap_in,
},
PlanStep::V4Swap {
c0_idx: c0_b,
c1_idx: c1_b,
fee: fee_b,
tick_spacing: ts_b,
hooks_idx: SENTINEL_NATIVE,
zfo: fb.zfo,
amount: b_swap_in,
in_currency: in_currency_b,
in_amount: b_swap_in,
out_currency: forward_b_cur,
out_amount: out_b,
},
PlanStep::V4TakeCompact {
currency_idx: forward_b,
currency_addr: forward_b_cur,
recipient_idx: v2c,
amount: out_b,
seeds_pool: None,
repays_flash: Some(fc.pool_address),
},
PlanStep::V4SettleDelta {
currency_idx: forward_a,
currency_addr: forward_a_cur,
},
];
let c_cb: Plan = vec![
PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: v2a,
amount: optimal_input,
seeds_pool: Some(fa.pool_address),
repays_flash: None,
},
PlanStep::V4Unlock {
inner: v4_inner,
pool_manager_idx: pm_idx,
},
];
let plan: Plan = vec![
PlanStep::SelfFund {
currency: weth,
amount: optimal_input,
},
mechanics::v2_flash(&mut at, fc, out_c, in_c, c_swap_in, c_cb)?,
];
Some((plan, at))
}
[Prot::V2, Prot::V4, Prot::V3] => {
if inputs.hop_outputs.contains(&0) || !fits_i128(optimal_input) {
return None;
}
let v4_swap_in = *inputs.consumed_inputs.get(1)?;
let c_swap_in = *inputs.consumed_inputs.get(2)?;
if !fits_i128(v4_swap_in) || !fits_i128(c_swap_in) {
return None;
}
let forward_a_cur = fa.out_currency;
let forward_b_cur = fb.out_currency;
let in_currency_b = fb.in_currency;
let mut at = v4_scaffold_table(inputs);
let pm_idx = at.add(pm).ok()?;
let forward_a = at.add(forward_a_cur).ok()?;
let forward_b = at.add(forward_b_cur).ok()?;
let v2a = at.add(fa.pool_address).ok()?;
let v3c = at.add(fc.pool_address).ok()?;
let fee_b = fb.swap_fee;
let ts_b = fb.tick_spacing;
let c0_b = at.add(fb.currency0_address).ok()?;
let c1_b = at.add(fb.currency1_address).ok()?;
let a_step = mechanics::v2_swap(&mut at, fa, v4_swap_in, pm_idx, None, false)?;
let v4_inner: Plan = vec![
PlanStep::V4Sync {
currency_idx: forward_a,
currency_addr: forward_a_cur,
},
a_step,
PlanStep::V4Settle {
currency_addr: forward_a_cur,
amount: v4_swap_in,
},
PlanStep::V4Swap {
c0_idx: c0_b,
c1_idx: c1_b,
fee: fee_b,
tick_spacing: ts_b,
hooks_idx: SENTINEL_NATIVE,
zfo: fb.zfo,
amount: v4_swap_in,
in_currency: in_currency_b,
in_amount: v4_swap_in,
out_currency: forward_b_cur,
out_amount: out_b,
},
PlanStep::V4TakeCompact {
currency_idx: forward_b,
currency_addr: forward_b_cur,
recipient_idx: v3c,
amount: c_swap_in,
seeds_pool: None,
repays_flash: Some(fc.pool_address),
},
PlanStep::V4SettleDelta {
currency_idx: forward_a,
currency_addr: forward_a_cur,
},
];
let c_cb: Plan = vec![
PlanStep::Erc20Transfer {
token_idx: SENTINEL_WETH,
token_addr: weth,
recipient_idx: v2a,
amount: optimal_input,
seeds_pool: Some(fa.pool_address),
repays_flash: None,
},
PlanStep::V4Unlock {
inner: v4_inner,
pool_manager_idx: pm_idx,
},
];
let plan: Plan = vec![
PlanStep::SelfFund {
currency: weth,
amount: optimal_input,
},
mechanics::v3_flash(
&mut at,
fc,
out_c,
c_swap_in,
false,
Some((SENTINEL_SELF, None, false)),
c_cb,
)?,
];
Some((plan, at))
}
_ => None,
}
}
#[cfg(test)]
mod rule_walker_tests {
#![expect(clippy::cast_possible_truncation, clippy::expect_used)]
use super::{derive, rule_walk_v2v3, rule_walk_v2v3_v4_mixed, rule_walk_v4_led};
use crate::composers::{
ComposerInputs, EncodeOptions, HopInfo, PathInfo, V2HopInfo, V3HopInfo, V4HopInfo,
};
use crate::encoders::enc_preamble;
use crate::grammar_ledger::Prot;
use crate::grammar_plan::plan_to_bytes;
use crate::grammar_walker::facts_for;
use alloy::primitives::{address, Address};
const WETH: Address = address!("C02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2");
const USDC: Address = address!("A0b86991c6218b36c1D19D4a2e9Eb0cE3606eB48");
const WBTC: Address = address!("2260FAC5E5542a773Aa44fBCfeDf7C193bc2C599");
const PM: Address = address!("000000000004444c5dc75cB358380D2e3dE08A90");
const EXEC: Address = address!("DeAd0000000000000000000000000000000000Be");
fn hop(prots: &[Prot], i: usize) -> HopInfo {
let cycle = [WETH, USDC, WBTC];
let in_t = cycle[i % 3];
let out_t = cycle[(i + 1) % 3];
match prots[i] {
Prot::V2 => HopInfo::V2(V2HopInfo {
pool_address: Address::from([0xA0 + i as u8; 20]),
token0_address: in_t,
token1_address: out_t,
fee: 30,
zfo: true,
}),
Prot::V3 => HopInfo::V3(V3HopInfo {
pool_address: Address::from([0xB0 + i as u8; 20]),
token0_address: in_t,
token1_address: out_t,
fee: 3000,
zfo: true,
}),
Prot::V4 => unreachable!("V4 excluded by the walker gate"),
}
}
fn full_bytes(plan: &crate::grammar_plan::Plan, at: &crate::encoders::AddressTable) -> Vec<u8> {
let mut b = enc_preamble(at);
b.extend_from_slice(&plan_to_bytes(plan, at));
b
}
#[test]
fn rule_walker_shadows_the_7_arms() {
let families: [[Prot; 3]; 7] = [
[Prot::V3, Prot::V3, Prot::V3],
[Prot::V3, Prot::V3, Prot::V2],
[Prot::V3, Prot::V2, Prot::V3],
[Prot::V3, Prot::V2, Prot::V2],
[Prot::V2, Prot::V2, Prot::V3],
[Prot::V2, Prot::V3, Prot::V3],
[Prot::V2, Prot::V3, Prot::V2],
];
let configs: [(u128, [u128; 3], [u128; 3]); 4] = [
(
1_000_000_000_000_000_000,
[1_000_000_000_000_000_000; 3],
[999_999_999_999_999_999; 3],
),
(
1_000_000_000_000_000_000,
[1_000_000_000_000_000_000; 3],
[1_000_000_000_000_000_000; 3],
),
(2u128.pow(95), [2u128.pow(95); 3], [2u128.pow(95) - 1; 3]),
(2u128.pow(95), [2u128.pow(95); 3], [2u128.pow(95); 3]),
];
for fam in families {
let hops: Vec<HopInfo> = (0..3).map(|i| hop(&fam, i)).collect();
let path = PathInfo::new(hops);
let label = format!("{}{}{}", fam[0] as u8, fam[1] as u8, fam[2] as u8);
for (ci, (optimal, outs, consumed)) in configs.iter().enumerate() {
let inputs = ComposerInputs {
executor_address: EXEC,
pool_manager_address: PM,
weth_address: WETH,
optimal_input: *optimal,
hop_outputs: outs,
consumed_inputs: consumed,
opts: EncodeOptions::default(),
};
let facts = facts_for(&path, &inputs).expect("facts exist");
let (plan_arm, at_arm) = derive(&facts, &inputs).expect("arm produces a plan");
let (plan_wlk, at_wlk) =
rule_walk_v2v3(&facts, &inputs).expect("walker produces a plan");
let bytes_arm = full_bytes(&plan_arm, &at_arm);
let bytes_wlk = full_bytes(&plan_wlk, &at_wlk);
assert_eq!(
bytes_arm,
bytes_wlk,
"walker diverged from derive for family {} config {} (bytes {} vs {})",
label,
ci,
bytes_arm.len(),
bytes_wlk.len(),
);
}
}
}
fn v4_led_hops(prots: &[Prot]) -> Vec<HopInfo> {
let cycle = [WETH, USDC, WBTC];
(0..3)
.map(|i| {
let in_t = cycle[i % 3];
let out_t = cycle[(i + 1) % 3];
match prots[i] {
Prot::V2 => HopInfo::V2(V2HopInfo {
pool_address: Address::from([0xA0 + i as u8; 20]),
token0_address: in_t,
token1_address: out_t,
fee: 30,
zfo: true,
}),
Prot::V3 => HopInfo::V3(V3HopInfo {
pool_address: Address::from([0xB0 + i as u8; 20]),
token0_address: in_t,
token1_address: out_t,
fee: 3000,
zfo: true,
}),
Prot::V4 => HopInfo::V4(V4HopInfo {
pool_manager_address: PM,
pool_id_hex: format!("0x{i:02x}"),
currency0_address: in_t,
currency1_address: out_t,
fee: 500,
tick_spacing: 10,
hook_address: Address::ZERO,
zfo: true,
}),
}
})
.collect()
}
#[test]
fn rule_walker_shadows_the_v4_led_group() {
let families: [[Prot; 3]; 9] = [
[Prot::V4, Prot::V4, Prot::V4],
[Prot::V4, Prot::V2, Prot::V2],
[Prot::V4, Prot::V2, Prot::V4],
[Prot::V4, Prot::V3, Prot::V3],
[Prot::V4, Prot::V3, Prot::V4],
[Prot::V4, Prot::V4, Prot::V2],
[Prot::V4, Prot::V4, Prot::V3],
[Prot::V4, Prot::V2, Prot::V3],
[Prot::V4, Prot::V3, Prot::V2],
];
let configs: [(u128, [u128; 3], [u128; 3]); 4] = [
(
1_000_000_000_000_000_000,
[1_000_000_000_000_000_000; 3],
[999_999_999_999_999_999; 3],
),
(
1_000_000_000_000_000_000,
[1_000_000_000_000_000_000; 3],
[1_000_000_000_000_000_000; 3],
),
(2u128.pow(95), [2u128.pow(95); 3], [2u128.pow(95) - 1; 3]),
(2u128.pow(95), [2u128.pow(95); 3], [2u128.pow(95); 3]),
];
let opts: [(&str, EncodeOptions); 3] = [
("base", EncodeOptions::default()),
(
"erc6909",
EncodeOptions {
erc6909_profit: true,
use_v4_batch: false,
..Default::default()
},
),
(
"batch",
EncodeOptions {
erc6909_profit: false,
use_v4_batch: true,
..Default::default()
},
),
];
for fam in families {
let hops = v4_led_hops(&fam);
let path = PathInfo::new(hops);
let label = format!("{}{}{}", fam[0] as u8, fam[1] as u8, fam[2] as u8);
for (oi, (olabel, opt)) in opts.iter().enumerate() {
for (ci, (optimal, outs, consumed)) in configs.iter().enumerate() {
let inputs = ComposerInputs {
executor_address: EXEC,
pool_manager_address: PM,
weth_address: WETH,
optimal_input: *optimal,
hop_outputs: outs,
consumed_inputs: consumed,
opts: *opt,
};
let facts = facts_for(&path, &inputs).expect("facts exist");
let arm = derive(&facts, &inputs);
let wlk = rule_walk_v4_led(&facts, &inputs);
assert_eq!(
arm.is_some(),
wlk.is_some(),
"walker/derive decline mismatch family {label} opt {olabel} cfg {ci}"
);
let (Some((plan_arm, at_arm)), Some((plan_wlk, at_wlk))) = (arm, wlk) else {
continue;
};
let bytes_arm = full_bytes(&plan_arm, &at_arm);
let bytes_wlk = full_bytes(&plan_wlk, &at_wlk);
assert_eq!(
bytes_arm,
bytes_wlk,
"walker diverged from derive for family {label} opt {olabel} cfg {ci} \
(bytes {} vs {})",
bytes_arm.len(),
bytes_wlk.len(),
);
let _ = oi;
}
}
}
}
#[test]
fn rule_walker_shadows_the_group_c() {
let families: [[Prot; 3]; 7] = [
[Prot::V2, Prot::V2, Prot::V4],
[Prot::V2, Prot::V4, Prot::V4],
[Prot::V2, Prot::V3, Prot::V4],
[Prot::V3, Prot::V2, Prot::V4],
[Prot::V3, Prot::V3, Prot::V4],
[Prot::V2, Prot::V4, Prot::V2],
[Prot::V2, Prot::V4, Prot::V3],
];
let configs: [(u128, [u128; 3], [u128; 3]); 4] = [
(
1_000_000_000_000_000_000,
[1_000_000_000_000_000_000; 3],
[999_999_999_999_999_999; 3],
),
(
1_000_000_000_000_000_000,
[1_000_000_000_000_000_000; 3],
[1_000_000_000_000_000_000; 3],
),
(2u128.pow(95), [2u128.pow(95); 3], [2u128.pow(95) - 1; 3]),
(2u128.pow(95), [2u128.pow(95); 3], [2u128.pow(95); 3]),
];
let opts: [(&str, EncodeOptions); 3] = [
("base", EncodeOptions::default()),
(
"erc6909",
EncodeOptions {
erc6909_profit: true,
use_v4_batch: false,
..Default::default()
},
),
(
"batch",
EncodeOptions {
erc6909_profit: false,
use_v4_batch: true,
..Default::default()
},
),
];
for fam in families {
let hops = v4_led_hops(&fam);
let path = PathInfo::new(hops);
let label = format!("{}{}{}", fam[0] as u8, fam[1] as u8, fam[2] as u8);
for (oi, (olabel, opt)) in opts.iter().enumerate() {
for (ci, (optimal, outs, consumed)) in configs.iter().enumerate() {
let inputs = ComposerInputs {
executor_address: EXEC,
pool_manager_address: PM,
weth_address: WETH,
optimal_input: *optimal,
hop_outputs: outs,
consumed_inputs: consumed,
opts: *opt,
};
let facts = facts_for(&path, &inputs).expect("facts exist");
let arm = derive(&facts, &inputs);
let wlk = rule_walk_v2v3_v4_mixed(&facts, &inputs);
assert_eq!(
arm.is_some(),
wlk.is_some(),
"walker/derive decline mismatch family {label} opt {olabel} cfg {ci}"
);
let (Some((plan_arm, at_arm)), Some((plan_wlk, at_wlk))) = (arm, wlk) else {
continue;
};
let bytes_arm = full_bytes(&plan_arm, &at_arm);
let bytes_wlk = full_bytes(&plan_wlk, &at_wlk);
assert_eq!(
bytes_arm,
bytes_wlk,
"walker diverged from derive for family {label} opt {olabel} cfg {ci} \
(bytes {} vs {})",
bytes_arm.len(),
bytes_wlk.len(),
);
let _ = oi;
}
}
}
}
}