use alloy_primitives::{Bytes, U256};
use alloy_sol_types::{SolError, sol};
sol! {
#[derive(Debug)]
interface IConditionalOrder {
error OrderNotValid(string reason);
error PollTryNextBlock(string reason);
error PollTryAtBlock(uint256 blockNumber, string reason);
error PollTryAtEpoch(uint256 timestamp, string reason);
error PollNever(string reason);
}
}
#[derive(Clone, Debug, Eq, PartialEq)]
#[non_exhaustive]
pub enum ConditionalOrderRevert {
OrderNotValid {
reason: String,
},
PollTryNextBlock {
reason: String,
},
PollTryAtBlock {
block_number: U256,
reason: String,
},
PollTryAtEpoch {
timestamp: U256,
reason: String,
},
PollNever {
reason: String,
},
Unknown {
data: Bytes,
},
}
impl ConditionalOrderRevert {
pub fn reason(&self) -> Option<&str> {
match self {
Self::OrderNotValid { reason }
| Self::PollTryNextBlock { reason }
| Self::PollTryAtBlock { reason, .. }
| Self::PollTryAtEpoch { reason, .. }
| Self::PollNever { reason } => Some(reason),
Self::Unknown { .. } => None,
}
}
}
pub fn decode_conditional_order_revert(data: &[u8]) -> ConditionalOrderRevert {
if data.len() < 4 {
return unknown_conditional_order_revert(data);
}
let selector: [u8; 4] = data[..4]
.try_into()
.expect("length checked before selector extraction");
match selector {
s if s == IConditionalOrder::OrderNotValid::SELECTOR => {
match IConditionalOrder::OrderNotValid::abi_decode(data) {
Ok(decoded) => ConditionalOrderRevert::OrderNotValid {
reason: decoded.reason,
},
Err(_) => unknown_conditional_order_revert(data),
}
}
s if s == IConditionalOrder::PollTryNextBlock::SELECTOR => {
match IConditionalOrder::PollTryNextBlock::abi_decode(data) {
Ok(decoded) => ConditionalOrderRevert::PollTryNextBlock {
reason: decoded.reason,
},
Err(_) => unknown_conditional_order_revert(data),
}
}
s if s == IConditionalOrder::PollTryAtBlock::SELECTOR => {
match IConditionalOrder::PollTryAtBlock::abi_decode(data) {
Ok(decoded) => ConditionalOrderRevert::PollTryAtBlock {
block_number: decoded.blockNumber,
reason: decoded.reason,
},
Err(_) => unknown_conditional_order_revert(data),
}
}
s if s == IConditionalOrder::PollTryAtEpoch::SELECTOR => {
match IConditionalOrder::PollTryAtEpoch::abi_decode(data) {
Ok(decoded) => ConditionalOrderRevert::PollTryAtEpoch {
timestamp: decoded.timestamp,
reason: decoded.reason,
},
Err(_) => unknown_conditional_order_revert(data),
}
}
s if s == IConditionalOrder::PollNever::SELECTOR => {
match IConditionalOrder::PollNever::abi_decode(data) {
Ok(decoded) => ConditionalOrderRevert::PollNever {
reason: decoded.reason,
},
Err(_) => unknown_conditional_order_revert(data),
}
}
_ => unknown_conditional_order_revert(data),
}
}
fn unknown_conditional_order_revert(data: &[u8]) -> ConditionalOrderRevert {
ConditionalOrderRevert::Unknown {
data: Bytes::copy_from_slice(data),
}
}
#[cfg(test)]
mod tests {
use alloy_primitives::{Bytes, U256, hex, keccak256};
use alloy_sol_types::SolError;
use super::*;
#[test]
fn conditional_order_error_selectors_match_keccak() {
let cases: &[(&[u8; 4], &[u8])] = &[
(
&IConditionalOrder::OrderNotValid::SELECTOR,
b"OrderNotValid(string)",
),
(
&IConditionalOrder::PollTryNextBlock::SELECTOR,
b"PollTryNextBlock(string)",
),
(
&IConditionalOrder::PollTryAtBlock::SELECTOR,
b"PollTryAtBlock(uint256,string)",
),
(
&IConditionalOrder::PollTryAtEpoch::SELECTOR,
b"PollTryAtEpoch(uint256,string)",
),
(
&IConditionalOrder::PollNever::SELECTOR,
b"PollNever(string)",
),
];
for (selector, signature) in cases {
assert_eq!(
selector.as_slice(),
&keccak256(signature)[..4],
"selector for {} does not match keccak256(signature)",
std::str::from_utf8(signature).unwrap(),
);
}
}
#[test]
fn decodes_order_not_valid_revert() {
let data = IConditionalOrder::OrderNotValid {
reason: "expired".to_string(),
}
.abi_encode();
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::OrderNotValid {
reason: "expired".to_string(),
}
);
}
#[test]
fn decodes_poll_try_next_block_revert() {
let data = IConditionalOrder::PollTryNextBlock {
reason: "not ready".to_string(),
}
.abi_encode();
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::PollTryNextBlock {
reason: "not ready".to_string(),
}
);
}
#[test]
fn decodes_poll_try_at_block_revert() {
let data = IConditionalOrder::PollTryAtBlock {
blockNumber: U256::from(12_345_678_u64),
reason: "wait for block".to_string(),
}
.abi_encode();
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::PollTryAtBlock {
block_number: U256::from(12_345_678_u64),
reason: "wait for block".to_string(),
}
);
}
#[test]
fn decodes_poll_try_at_epoch_revert() {
let data = IConditionalOrder::PollTryAtEpoch {
timestamp: U256::from(1_700_000_000_u64),
reason: "wait for time".to_string(),
}
.abi_encode();
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::PollTryAtEpoch {
timestamp: U256::from(1_700_000_000_u64),
reason: "wait for time".to_string(),
}
);
}
#[test]
fn decodes_poll_never_revert() {
let data = IConditionalOrder::PollNever {
reason: "cancelled".to_string(),
}
.abi_encode();
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::PollNever {
reason: "cancelled".to_string(),
}
);
}
#[test]
fn unknown_selector_preserves_raw_payload() {
let data = Bytes::from_static(&hex!("7a9332340000000000000000000000000000000000000000"));
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::Unknown { data }
);
}
#[test]
fn malformed_known_selector_preserves_raw_payload() {
let mut data = IConditionalOrder::PollTryAtBlock::SELECTOR.to_vec();
data.extend_from_slice(&hex!("000102"));
let data = Bytes::from(data);
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::Unknown { data }
);
}
#[test]
fn truncated_revert_preserves_raw_payload() {
let data = Bytes::from_static(&hex!("010203"));
assert_eq!(
decode_conditional_order_revert(&data),
ConditionalOrderRevert::Unknown { data }
);
}
#[test]
fn known_revert_reason_is_exposed() {
let revert = ConditionalOrderRevert::PollTryNextBlock {
reason: "wait".to_string(),
};
assert_eq!(revert.reason(), Some("wait"));
assert_eq!(
ConditionalOrderRevert::Unknown { data: Bytes::new() }.reason(),
None
);
}
}