use std::cmp::Ordering;
use std::fmt;
use crate::Error;
pub const SELECTOR_SWAP_EXACT_ETH_FOR_TOKENS: &str = "0x6bf05b01";
pub const SELECTOR_SWAP_ETH_FOR_EXACT_TOKENS: &str = "0x0541a872";
pub const SELECTOR_SWAP_EXACT_TOKENS_FOR_ETH: &str = "0xeacad12a";
pub const SELECTOR_GET_SWAP_OUTPUT: &str = "0x908235cc";
pub const SELECTOR_GET_FIRST_BUY_FEE: &str = "0xb3cd4902";
pub const SELECTOR_GET_MAX_SELLABLE_ETH: &str = "0x82bfb367";
pub const SELECTOR_TOKEN_POOLS: &str = "0xc3d2c3c1";
pub const SELECTOR_APPROVE: &str = "0x095ea7b3";
pub const SELECTOR_ALLOWANCE: &str = "0xdd62ed3e";
pub const SELECTOR_BALANCE_OF: &str = "0x70a08231";
pub const SELECTOR_PLATFORM_BUY_FEE_BPS: &str = "0x04357acd";
pub const SELECTOR_TOKEN_OWNER_FEE_BPS: &str = "0xfa3746d8";
pub const SELECTOR_PAUSED: &str = "0x5c975abb";
fn invalid(message: impl Into<String>) -> Error {
Error::InvalidArgument {
message: message.into(),
}
}
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash, Default)]
pub struct U256([u8; 32]);
impl U256 {
pub const ZERO: U256 = U256([0u8; 32]);
pub const MAX: U256 = U256([0xffu8; 32]);
pub fn from_u64(value: u64) -> Self {
let mut bytes = [0u8; 32];
bytes[24..].copy_from_slice(&value.to_be_bytes());
U256(bytes)
}
pub fn from_be_bytes(bytes: [u8; 32]) -> Self {
U256(bytes)
}
pub fn to_be_bytes(self) -> [u8; 32] {
self.0
}
pub fn is_zero(self) -> bool {
self.0.iter().all(|&byte| byte == 0)
}
pub fn from_dec_str(name: &str, value: &str) -> Result<Self, Error> {
if value.is_empty() {
return Err(invalid(format!("{name} must not be empty")));
}
if value.contains('.') {
return Err(invalid(format!(
"{name} must be a whole amount in wei, got {value:?} — decimal input \
looks like whole units, convert it with parse_units first"
)));
}
let mut acc = U256::ZERO;
for character in value.chars() {
let digit = character
.to_digit(10)
.ok_or_else(|| invalid(format!("{name} must be base-10 digits, got {value:?}")))?;
acc = acc
.mul_small(10)
.and_then(|scaled| scaled.add_small(digit))
.ok_or_else(|| invalid(format!("{name} exceeds uint256")))?;
}
Ok(acc)
}
pub fn from_hex_word(word: &str) -> Result<Self, Error> {
let trimmed = word.strip_prefix("0x").unwrap_or(word);
if trimmed.len() != 64 {
return Err(invalid(format!(
"expected a 32-byte word, got {} hex characters",
trimmed.len()
)));
}
let mut bytes = [0u8; 32];
for (index, byte) in bytes.iter_mut().enumerate() {
*byte = u8::from_str_radix(&trimmed[index * 2..index * 2 + 2], 16)
.map_err(|_| invalid(format!("word is not valid hex: {word}")))?;
}
Ok(U256(bytes))
}
pub fn to_hex_word(self) -> String {
self.0.iter().map(|byte| format!("{byte:02x}")).collect()
}
pub fn checked_add(self, other: Self) -> Option<Self> {
let mut out = [0u8; 32];
let mut carry = 0u16;
for index in (0..32).rev() {
let sum = u16::from(self.0[index]) + u16::from(other.0[index]) + carry;
out[index] = sum as u8;
carry = sum >> 8;
}
(carry == 0).then_some(U256(out))
}
pub fn checked_sub(self, other: Self) -> Option<Self> {
let mut out = [0u8; 32];
let mut borrow = 0i16;
for index in (0..32).rev() {
let diff = i16::from(self.0[index]) - i16::from(other.0[index]) - borrow;
if diff < 0 {
out[index] = (diff + 256) as u8;
borrow = 1;
} else {
out[index] = diff as u8;
borrow = 0;
}
}
(borrow == 0).then_some(U256(out))
}
pub fn mul_small(self, factor: u32) -> Option<Self> {
let mut out = [0u8; 32];
let mut carry = 0u64;
for index in (0..32).rev() {
let product = u64::from(self.0[index]) * u64::from(factor) + carry;
out[index] = product as u8;
carry = product >> 8;
}
(carry == 0).then_some(U256(out))
}
fn add_small(self, digit: u32) -> Option<Self> {
self.checked_add(U256::from_u64(u64::from(digit)))
}
pub fn mul_div(self, factor: Self, divisor: Self) -> Option<Self> {
if divisor.is_zero() {
return None;
}
let mut product = [0u8; 64];
for i in (0..32).rev() {
let mut carry = 0u32;
for j in (0..32).rev() {
let at = i + j + 1;
let sum =
u32::from(product[at]) + u32::from(self.0[i]) * u32::from(factor.0[j]) + carry;
product[at] = sum as u8;
carry = sum >> 8;
}
product[i] = carry as u8;
}
let mut quotient = [0u8; 64];
let mut remainder = [0u8; 32];
for bit in 0..512 {
let mut carry = (product[bit / 8] >> (7 - bit % 8)) & 1;
for byte in remainder.iter_mut().rev() {
let shifted = (u16::from(*byte) << 1) | u16::from(carry);
*byte = shifted as u8;
carry = (shifted >> 8) as u8;
}
if carry == 1 || U256(remainder) >= divisor {
remainder = U256(remainder).wrapping_sub(divisor).0;
quotient[bit / 8] |= 1 << (7 - bit % 8);
}
}
if quotient[..32].iter().any(|&byte| byte != 0) {
return None;
}
let mut out = [0u8; 32];
out.copy_from_slice("ient[32..]);
Some(U256(out))
}
fn wrapping_sub(self, other: Self) -> Self {
let mut out = [0u8; 32];
let mut borrow = 0i16;
for index in (0..32).rev() {
let diff = i16::from(self.0[index]) - i16::from(other.0[index]) - borrow;
if diff < 0 {
out[index] = (diff + 256) as u8;
borrow = 1;
} else {
out[index] = diff as u8;
borrow = 0;
}
}
U256(out)
}
pub fn div_rem_small(self, divisor: u32) -> (Self, u32) {
assert!(divisor != 0, "division by zero");
let mut out = [0u8; 32];
let mut remainder = 0u64;
for (slot, &byte) in out.iter_mut().zip(self.0.iter()) {
let current = (remainder << 8) | u64::from(byte);
*slot = (current / u64::from(divisor)) as u8;
remainder = current % u64::from(divisor);
}
(U256(out), remainder as u32)
}
}
impl fmt::Display for U256 {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
if self.is_zero() {
return f.write_str("0");
}
let mut digits = Vec::new();
let mut value = *self;
while !value.is_zero() {
let (quotient, remainder) = value.div_rem_small(10);
digits.push(b'0' + remainder as u8);
value = quotient;
}
digits.reverse();
f.write_str(&String::from_utf8(digits).expect("ascii digits"))
}
}
impl PartialOrd for U256 {
fn partial_cmp(&self, other: &Self) -> Option<Ordering> {
Some(self.cmp(other))
}
}
impl Ord for U256 {
fn cmp(&self, other: &Self) -> Ordering {
self.0.cmp(&other.0)
}
}
pub fn normalize_address(name: &str, value: &str) -> Result<String, Error> {
let body = value.strip_prefix("0x").ok_or_else(|| {
invalid(format!(
"{name} must be a 0x-prefixed 20-byte address, got {value:?}"
))
})?;
if body.len() != 40 || !body.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(invalid(format!(
"{name} must be a 0x-prefixed 20-byte address, got {value:?}"
)));
}
Ok(value.to_ascii_lowercase())
}
pub fn parse_units(value: &str, decimals: u32) -> Result<U256, Error> {
let (whole, fraction) = match value.split_once('.') {
Some((whole, fraction)) => (whole, fraction),
None => (value, ""),
};
if fraction.len() > decimals as usize {
return Err(invalid(format!(
"amount {value:?} has {} decimal places, more than the {decimals} this token \
carries — the extra digits would be silently truncated",
fraction.len()
)));
}
if !whole.chars().all(|c| c.is_ascii_digit())
|| !fraction.chars().all(|c| c.is_ascii_digit())
|| (whole.is_empty() && fraction.is_empty())
{
return Err(invalid(format!(
"amount must be a non-negative decimal string, got {value:?}"
)));
}
let padded = format!(
"{}{}{}",
if whole.is_empty() { "0" } else { whole },
fraction,
"0".repeat(decimals as usize - fraction.len())
);
U256::from_dec_str("amount", &padded)
}
pub fn format_units(value: U256, decimals: u32) -> String {
let digits = value.to_string();
let decimals = decimals as usize;
let (whole, fraction) = if digits.len() > decimals {
digits.split_at(digits.len() - decimals)
} else {
("0", digits.as_str())
};
let padded = format!(
"{}{}",
"0".repeat(decimals.saturating_sub(fraction.len())),
fraction
);
let trimmed = padded.trim_end_matches('0');
if trimmed.is_empty() {
whole.to_string()
} else {
format!("{whole}.{trimmed}")
}
}
pub fn address_word(address: &str) -> Result<String, Error> {
let normalized = normalize_address("address", address)?;
Ok(format!("{:0>64}", &normalized[2..]))
}
pub fn uint256_word(value: U256) -> String {
value.to_hex_word()
}
pub fn bool_word(value: bool) -> String {
U256::from_u64(u64::from(value)).to_hex_word()
}
pub fn encode_call(selector: &str, words: &[String]) -> String {
let mut out = String::from(selector);
for word in words {
out.push_str(word);
}
out
}
fn hex_body(data: &str) -> Result<&str, Error> {
let body = data.strip_prefix("0x").unwrap_or(data);
if body.len() % 2 != 0 || !body.chars().all(|c| c.is_ascii_hexdigit()) {
return Err(invalid(format!("expected hex data, got {data:?}")));
}
Ok(body)
}
pub fn decode_uint256_at(data: &str, index: usize) -> Result<U256, Error> {
let body = hex_body(data)?;
let start = index * 64;
if body.len() < start + 64 {
return Err(invalid(format!(
"response has {} words, expected at least {}",
body.len() / 64,
index + 1
)));
}
U256::from_hex_word(&body[start..start + 64])
}
pub fn decode_bool_at(data: &str, index: usize) -> Result<bool, Error> {
Ok(!decode_uint256_at(data, index)?.is_zero())
}
pub fn decode_address_at(data: &str, index: usize) -> Result<String, Error> {
let word = decode_uint256_at(data, index)?.to_hex_word();
Ok(format!("0x{}", &word[24..]))
}
#[cfg(test)]
mod tests {
use super::*;
const UINT256_MAX: &str =
"115792089237316195423570985008687907853269984665640564039457584007913129639935";
const SPREAD: [&str; 11] = [
"0",
"1",
"2",
"255",
"256",
"18446744073709551616",
"340282366920938463463374607431768211455",
"8337685194944913640",
"1061715547304072332277807447",
"1000000000000000000000000",
UINT256_MAX,
];
fn u256(value: &str) -> U256 {
U256::from_dec_str("value", value).expect("a uint256 in range")
}
fn digits(value: &str) -> Vec<u8> {
value.bytes().rev().map(|byte| byte - b'0').collect()
}
fn text(mut value: Vec<u8>) -> String {
while value.len() > 1 && value.last() == Some(&0) {
value.pop();
}
value.iter().rev().map(|d| char::from(b'0' + d)).collect()
}
fn multiply(left: &[u8], right: &[u8]) -> Vec<u8> {
let mut out = vec![0u8; left.len() + right.len() + 1];
for (i, &x) in left.iter().enumerate() {
let mut carry = 0u32;
for (j, &y) in right.iter().enumerate() {
let sum = u32::from(out[i + j]) + u32::from(x) * u32::from(y) + carry;
out[i + j] = (sum % 10) as u8;
carry = sum / 10;
}
let mut at = i + right.len();
while carry > 0 {
let sum = u32::from(out[at]) + carry;
out[at] = (sum % 10) as u8;
carry = sum / 10;
at += 1;
}
}
out
}
fn compare(left: &[u8], right: &[u8]) -> Ordering {
let (left, right) = (trimmed(left), trimmed(right));
left.len()
.cmp(&right.len())
.then_with(|| left.iter().rev().cmp(right.iter().rev()))
}
fn trimmed(value: &[u8]) -> &[u8] {
let mut end = value.len();
while end > 1 && value[end - 1] == 0 {
end -= 1;
}
&value[..end]
}
fn subtract(left: &[u8], right: &[u8]) -> Vec<u8> {
let mut out = left.to_vec();
let mut borrow = 0i16;
for (index, slot) in out.iter_mut().enumerate() {
let take = i16::from(*right.get(index).unwrap_or(&0)) + borrow;
let digit = i16::from(*slot) - take;
if digit < 0 {
*slot = (digit + 10) as u8;
borrow = 1;
} else {
*slot = digit as u8;
borrow = 0;
}
}
assert_eq!(borrow, 0, "the reference never subtracts past zero");
out
}
fn divide(left: &[u8], right: &[u8]) -> Vec<u8> {
let mut quotient = vec![0u8; left.len()];
let mut remainder: Vec<u8> = vec![0];
for index in (0..left.len()).rev() {
remainder.insert(0, left[index]);
let mut digit = 0u8;
while compare(&remainder, right) != Ordering::Less {
remainder = subtract(&remainder, right);
digit += 1;
}
quotient[index] = digit;
}
quotient
}
fn reference_mul_div(a: &str, b: &str, c: &str) -> Option<String> {
if c.bytes().all(|byte| byte == b'0') {
return None;
}
Some(text(divide(&multiply(&digits(a), &digits(b)), &digits(c))))
}
#[test]
fn the_reference_divides_the_way_the_evm_does() {
assert_eq!(reference_mul_div("7", "3", "2").as_deref(), Some("10"));
assert_eq!(reference_mul_div("1", "1", "2").as_deref(), Some("0"));
assert_eq!(reference_mul_div("0", "5", "7").as_deref(), Some("0"));
assert_eq!(
reference_mul_div("255", "256", "1").as_deref(),
Some("65280")
);
assert_eq!(reference_mul_div("5", "5", "0"), None);
}
#[test]
fn mul_div_agrees_with_a_slow_reference_across_the_spread() {
for a in SPREAD {
for b in SPREAD {
for c in SPREAD {
let expected = reference_mul_div(a, b, c);
let actual = u256(a).mul_div(u256(b), u256(c));
match (&expected, actual) {
(Some(quotient), _) if quotient.len() > UINT256_MAX.len() => {
assert!(actual.is_none(), "{a} * {b} / {c} = {quotient} overflows");
}
(Some(quotient), Some(got)) => {
assert_eq!(&got.to_string(), quotient, "{a} * {b} / {c}");
}
(Some(quotient), None) => {
assert!(
compare(&digits(quotient), &digits(UINT256_MAX))
== Ordering::Greater,
"{a} * {b} / {c} = {quotient} fits and should not be refused"
);
}
(None, got) => assert_eq!(got, None, "a zero divisor has no answer"),
}
}
}
}
}
#[test]
fn mul_div_refuses_a_zero_divisor_rather_than_panicking() {
assert_eq!(U256::MAX.mul_div(U256::MAX, U256::ZERO), None);
assert_eq!(U256::ZERO.mul_div(U256::ZERO, U256::ZERO), None);
}
#[test]
fn mul_div_carries_a_product_that_does_not_fit_in_a_uint256() {
let two_255 =
u256("57896044618658097711785492504343953926634992332820282019728792003956564819968");
assert_eq!(two_255.mul_div(two_255, two_255), Some(two_255));
assert!(
two_255.mul_div(two_255, U256::from_u64(2)).is_none(),
"2^509 does not fit"
);
assert_eq!(U256::MAX.mul_div(U256::MAX, U256::MAX), Some(U256::MAX));
assert_eq!(
U256::MAX.mul_div(U256::MAX, U256::MAX.checked_sub(U256::from_u64(1)).unwrap()),
None
);
}
#[test]
fn mul_div_handles_zero_and_one_without_a_special_case() {
assert_eq!(
U256::ZERO.mul_div(U256::MAX, U256::from_u64(1)),
Some(U256::ZERO)
);
assert_eq!(
U256::MAX.mul_div(U256::from_u64(1), U256::from_u64(1)),
Some(U256::MAX)
);
assert_eq!(
U256::from_u64(1).mul_div(U256::from_u64(1), U256::MAX),
Some(U256::ZERO),
"flooring, not rounding"
);
}
#[test]
fn mul_div_reproduces_the_contracts_get_amount_in_on_a_live_curve() {
let virtual_eth = u256("8832685194944913640");
let virtual_token = u256("1002215046118283895434733086");
let wanted = u256("1000000000000000000000000");
let before_fee = virtual_eth
.mul_div(wanted, virtual_token.checked_sub(wanted).unwrap())
.expect("the quotient is small")
.checked_add(U256::from_u64(1))
.expect("the contract's +1");
assert_eq!(before_fee.to_string(), "8821966099280351");
let cost = before_fee
.mul_div(U256::from_u64(10_000), U256::from_u64(10_000 - 100))
.expect("the gross-up");
assert_eq!(cost.to_string(), "8911076867959950");
}
}