celox-testbench 0.3.1

Source-independent testbench bytecode contracts for Celox
Documentation
use num_bigint::{BigInt, BigUint, Sign};
use num_traits::ToPrimitive as _;

pub struct DisplayFormatArg<'a> {
    pub value: &'a BigUint,
    pub mask: Option<&'a BigUint>,
    pub width: usize,
    pub signed: bool,
    pub is_string: bool,
}

fn bit(value: &BigUint, bit: usize) -> bool {
    ((value >> bit) & BigUint::from(1u8)) != BigUint::from(0u8)
}

fn has_mask(arg: &DisplayFormatArg<'_>) -> bool {
    let Some(mask) = arg.mask else {
        return false;
    };
    for bit_idx in 0..arg.width {
        if bit(mask, bit_idx) {
            return true;
        }
    }
    false
}

fn masked_value(value: &BigUint, width: usize) -> BigUint {
    if width > 0 {
        value & ((BigUint::from(1u8) << width) - BigUint::from(1u8))
    } else {
        BigUint::from(0u8)
    }
}

fn value_to_signed_bigint(value: &BigUint, width: usize) -> BigInt {
    if width == 0 {
        return BigInt::from(0);
    }
    let unsigned = masked_value(value, width);
    let sign_bit = BigUint::from(1u8) << (width - 1);
    if (&unsigned & &sign_bit) != BigUint::from(0u8) {
        BigInt::from_biguint(Sign::Plus, unsigned) - (BigInt::from(1u8) << width)
    } else {
        BigInt::from_biguint(Sign::Plus, unsigned)
    }
}

fn value_to_utf8(value: &BigUint, width: usize) -> Option<String> {
    if !width.is_multiple_of(8) {
        return None;
    }
    let num_bytes = width / 8;
    let mut bytes = vec![0u8; num_bytes];
    let mut payload = masked_value(value, width);
    let byte_mask = BigUint::from(0xffu64);
    for idx in (0..num_bytes).rev() {
        bytes[idx] = (&payload & &byte_mask).to_u64().unwrap_or(0) as u8;
        payload >>= 8;
    }
    String::from_utf8(bytes).ok()
}

fn format_binary(arg: &DisplayFormatArg<'_>) -> String {
    let digits = arg.width.max(1);
    if !has_mask(arg) {
        let mut out = masked_value(arg.value, arg.width).to_str_radix(2);
        if out.len() < digits {
            out.insert_str(0, &"0".repeat(digits - out.len()));
        }
        return out;
    }
    let mask = arg.mask.expect("masked argument");
    let mut out = String::with_capacity(digits);
    for bit_idx in (0..digits).rev() {
        if bit(mask, bit_idx) {
            out.push('x');
        } else if bit(arg.value, bit_idx) {
            out.push('1');
        } else {
            out.push('0');
        }
    }
    out
}

fn format_masked_radix(arg: &DisplayFormatArg<'_>, bits_per_digit: usize) -> String {
    let mask = arg.mask.expect("masked argument");
    let digits = arg.width.div_ceil(bits_per_digit).max(1);
    let mut out = String::with_capacity(digits);
    for digit_idx in (0..digits).rev() {
        let start = digit_idx * bits_per_digit;
        let end = (start + bits_per_digit).min(arg.width);
        if (start..end).any(|bit_idx| bit(mask, bit_idx)) {
            out.push('x');
            continue;
        }
        let mut digit = 0u32;
        for bit_idx in start..end {
            if bit(arg.value, bit_idx) {
                digit |= 1 << (bit_idx - start);
            }
        }
        out.push(char::from_digit(digit, 1 << bits_per_digit).unwrap());
    }
    out
}

pub fn format_display_arg(arg: &DisplayFormatArg<'_>, spec: Option<char>) -> String {
    if arg.is_string {
        return value_to_utf8(arg.value, arg.width).unwrap_or_else(|| format!("{:?}", arg.value));
    }
    match spec.unwrap_or('d') {
        'b' | 'B' => format_binary(arg),
        'o' | 'O' => {
            if has_mask(arg) {
                format_masked_radix(arg, 3)
            } else {
                masked_value(arg.value, arg.width).to_str_radix(8)
            }
        }
        'x' | 'h' => {
            if has_mask(arg) {
                format_masked_radix(arg, 4)
            } else {
                masked_value(arg.value, arg.width).to_str_radix(16)
            }
        }
        'X' | 'H' => {
            let mut out = if has_mask(arg) {
                format_masked_radix(arg, 4)
            } else {
                masked_value(arg.value, arg.width).to_str_radix(16)
            };
            out.make_ascii_uppercase();
            out
        }
        'd' | 'D' | 'i' | 'I' => {
            if has_mask(arg) {
                "x".to_string()
            } else if arg.signed {
                value_to_signed_bigint(arg.value, arg.width).to_string()
            } else {
                masked_value(arg.value, arg.width).to_string()
            }
        }
        'c' | 'C' => {
            if has_mask(arg) {
                "x".to_string()
            } else {
                char::from((masked_value(arg.value, arg.width).to_u64().unwrap_or(0) & 0xff) as u8)
                    .to_string()
            }
        }
        's' | 'S' => {
            if has_mask(arg) {
                "x".to_string()
            } else {
                value_to_utf8(arg.value, arg.width).unwrap_or_else(|| format!("{:?}", arg.value))
            }
        }
        _ => {
            if has_mask(arg) {
                "x".to_string()
            } else if arg.signed {
                value_to_signed_bigint(arg.value, arg.width).to_string()
            } else {
                masked_value(arg.value, arg.width).to_string()
            }
        }
    }
}