eth-valkyoth-codec 0.7.0

Bounded no_std Ethereum wire codec policy.
Documentation
use crate::{DecodeError, DecodeLimits};

use super::{
    super::{RlpItem, RlpListForm, RlpScalarForm, decode_rlp_list, decode_rlp_list_partial},
    vec,
    vec::Vec,
};

#[test]
fn decodes_empty_list() {
    assert!(matches!(
        decode_rlp_list(&[0xc0], DecodeLimits::TEST_FIXTURE),
        Ok(list)
            if list.payload().is_empty()
                && list.encoded_len() == 1
                && list.header_len() == 1
                && list.item_count() == 0
                && list.form() == RlpListForm::ShortList
                && list.is_empty()
    ));
}

#[test]
fn list_equality_ignores_decode_policy() -> Result<(), DecodeError> {
    let fixture = decode_rlp_list(&[0xc0], DecodeLimits::TEST_FIXTURE)?;
    let deployment = decode_rlp_list(&[0xc0], DecodeLimits::DEPLOYMENT_STARTING_POINT)?;

    assert_eq!(fixture, deployment);
    Ok(())
}

#[test]
fn decodes_short_list_with_scalars() {
    let input = [0xc8, 0x83, b'c', b'a', b't', 0x83, b'd', b'o', b'g'];

    assert!(matches!(
        decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE),
        Ok(list)
            if list.payload() == &input[1..]
                && list.encoded_len() == input.len()
                && list.item_count() == 2
                && list.form() == RlpListForm::ShortList
    ));
}

#[test]
fn decodes_official_nested_list_examples() -> Result<(), DecodeError> {
    let set = [0xc0];
    let empty_set = [0xc1, 0xc0];
    let two_empty_sets = [0xc2, 0xc0, 0xc0];
    let mixed = [0xc7, b'z', 0xc1, b'y', 0xc3, b'x', b'y', b'z'];

    assert!(decode_rlp_list(&set, DecodeLimits::TEST_FIXTURE)?.is_empty());
    assert_eq!(
        decode_rlp_list(&empty_set, DecodeLimits::TEST_FIXTURE)?.item_count(),
        1
    );
    assert_eq!(
        decode_rlp_list(&two_empty_sets, DecodeLimits::TEST_FIXTURE)?.item_count(),
        2
    );

    let list = decode_rlp_list(&mixed, DecodeLimits::TEST_FIXTURE)?;
    let mut items = list.items();
    assert!(matches!(
        items.next(),
        Some(Ok(RlpItem::Scalar(scalar))) if scalar.payload() == b"z"
    ));
    assert!(matches!(
        items.next(),
        Some(Ok(RlpItem::List(child))) if child.item_count() == 1
    ));
    assert!(matches!(
        items.next(),
        Some(Ok(RlpItem::List(child))) if child.item_count() == 3
    ));
    assert!(items.next().is_none());
    Ok(())
}

#[test]
fn decodes_nested_short_lists() {
    let input = [0xc3, 0xc0, 0xc1, 0xc0];

    assert!(matches!(
        decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE),
        Ok(list)
            if list.payload() == &input[1..]
                && list.encoded_len() == input.len()
                && list.item_count() == 2
    ));
}

#[test]
fn iterates_empty_list_items() -> Result<(), DecodeError> {
    let list = decode_rlp_list(&[0xc0], DecodeLimits::TEST_FIXTURE)?;
    let mut items = list.items();

    assert_eq!(items.remaining(), 0);
    assert_eq!(items.size_hint(), (0, Some(0)));
    assert!(items.next().is_none());
    Ok(())
}

#[test]
fn iterates_scalar_list_items() -> Result<(), DecodeError> {
    let input = [0xc8, 0x83, b'c', b'a', b't', 0x83, b'd', b'o', b'g'];
    let list = decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE)?;
    let mut items = list.items();

    assert_eq!(items.remaining(), 2);
    assert!(matches!(
        items.next(),
        Some(Ok(RlpItem::Scalar(scalar)))
            if scalar.payload() == b"cat"
                && scalar.encoded_len() == 4
                && scalar.form() == RlpScalarForm::ShortString
    ));
    assert_eq!(items.remaining(), 1);
    assert!(matches!(
        items.next(),
        Some(Ok(RlpItem::Scalar(scalar)))
            if scalar.payload() == b"dog"
                && scalar.encoded_len() == 4
                && scalar.form() == RlpScalarForm::ShortString
    ));
    assert!(items.next().is_none());
    Ok(())
}

#[test]
fn iterates_nested_list_items() -> Result<(), DecodeError> {
    let input = [0xc4, 0xc0, 0xc2, 0x80, 0x01];
    let list = decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE)?;
    let mut items = list.items();

    assert!(matches!(
        items.next(),
        Some(Ok(RlpItem::List(child))) if child.is_empty() && child.item_count() == 0
    ));

    let Some(Ok(RlpItem::List(child))) = items.next() else {
        return Err(DecodeError::Malformed);
    };
    assert_eq!(child.item_count(), 2);
    let mut child_items = child.items();
    assert!(matches!(
        child_items.next(),
        Some(Ok(RlpItem::Scalar(scalar)))
            if scalar.payload().is_empty() && scalar.form() == RlpScalarForm::ShortString
    ));
    assert!(matches!(
        child_items.next(),
        Some(Ok(RlpItem::Scalar(scalar)))
            if scalar.payload() == [0x01] && scalar.form() == RlpScalarForm::SingleByte
    ));
    assert!(items.next().is_none());
    Ok(())
}

#[test]
fn item_accessors_return_expected_variant() -> Result<(), DecodeError> {
    let input = [0xc3, 0x80, 0xc1, 0x01];
    let list = decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE)?;
    let mut items = list.items();

    let Some(Ok(scalar_item)) = items.next() else {
        return Err(DecodeError::Malformed);
    };
    assert!(scalar_item.is_scalar());
    assert!(!scalar_item.is_list());
    assert_eq!(scalar_item.header_len(), 1);
    assert!(scalar_item.as_list().is_none());
    assert!(matches!(
        scalar_item.as_scalar(),
        Some(scalar) if scalar.payload().is_empty()
    ));

    let Some(Ok(list_item)) = items.next() else {
        return Err(DecodeError::Malformed);
    };
    assert!(list_item.is_list());
    assert!(!list_item.is_scalar());
    assert_eq!(list_item.header_len(), 1);
    assert!(list_item.as_scalar().is_none());
    assert!(matches!(
        list_item.as_list(),
        Some(child) if child.item_count() == 1
    ));
    Ok(())
}

#[test]
fn list_item_iterator_is_fused() -> Result<(), DecodeError> {
    fn assert_fused<I: core::iter::FusedIterator>(_iterator: &I) {}

    let list = decode_rlp_list(&[0xc0], DecodeLimits::TEST_FIXTURE)?;
    let mut items = list.items();
    assert_fused(&items);

    assert!(items.next().is_none());
    assert!(items.next().is_none());
    Ok(())
}

#[test]
fn decodes_long_list() {
    let mut input = vec![0xf8, 56];
    input.extend_from_slice(&[0x80; 56]);

    assert!(matches!(
        decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE),
        Ok(list)
            if list.payload().len() == 56
                && list.encoded_len() == 58
                && list.header_len() == 2
                && list.item_count() == 56
                && list.form() == RlpListForm::LongList
    ));
}

#[test]
fn partial_list_decoder_leaves_trailing_bytes_to_caller() {
    let mut accumulator = DecodeLimits::TEST_FIXTURE.accumulator();

    assert!(matches!(
        decode_rlp_list_partial(&[0xc1, 0x80, 0x80], &mut accumulator),
        Ok(list) if list.item_count() == 1 && list.encoded_len() == 2
    ));
    assert_eq!(accumulator.total_items(), 2);
}

#[test]
fn exact_list_decoder_rejects_trailing_bytes() {
    assert_eq!(
        decode_rlp_list(&[0xc1, 0x80, 0x80], DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::TrailingBytes)
    );
}

#[test]
fn rejects_scalar_for_list_decoder() {
    assert_eq!(
        decode_rlp_list(&[0x80], DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::UnexpectedScalar)
    );
}

#[test]
fn rejects_short_list_missing_payload() {
    assert_eq!(
        decode_rlp_list(&[0xc2, 0x80], DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::OffsetOutOfBounds)
    );
}

#[test]
fn rejects_long_list_missing_length_bytes() {
    assert_eq!(
        decode_rlp_list(&[0xf9, 0x01], DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::OffsetOutOfBounds)
    );
}

#[test]
fn rejects_long_list_missing_payload() {
    assert_eq!(
        decode_rlp_list(&[0xf8, 56, 0x80], DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::OffsetOutOfBounds)
    );
}

#[test]
fn rejects_long_list_with_leading_zero_length() {
    let mut input = vec![0xf9, 0, 56];
    input.extend_from_slice(&[0x80; 56]);

    assert_eq!(
        decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::Malformed)
    );
}

#[test]
fn rejects_long_list_length_overflow() {
    let input = [0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff];

    assert_eq!(
        decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::LengthOverflow)
    );
}

#[test]
fn rejects_long_list_used_for_short_payload() {
    let mut input = vec![0xf8, 55];
    input.extend_from_slice(&[0x80; 55]);

    assert_eq!(
        decode_rlp_list(&input, DecodeLimits::TEST_FIXTURE),
        Err(DecodeError::Malformed)
    );
}

#[test]
fn enforces_list_item_budget() {
    let limits = DecodeLimits {
        max_list_items: 1,
        ..DecodeLimits::TEST_FIXTURE
    };

    assert_eq!(
        decode_rlp_list(&[0xc2, 0x80, 0x80], limits),
        Err(DecodeError::ListTooLong)
    );
}

#[test]
fn enforces_list_nesting_budget() {
    let limits = DecodeLimits {
        max_nesting_depth: 1,
        ..DecodeLimits::TEST_FIXTURE
    };

    assert_eq!(
        decode_rlp_list(&[0xc1, 0xc0], limits),
        Err(DecodeError::NestingTooDeep)
    );
}

#[test]
fn enforces_deep_list_nesting_budget() -> Result<(), DecodeError> {
    let input = nested_empty_list(65)?;
    let limits = DecodeLimits {
        max_input_bytes: input.len(),
        max_nesting_depth: 64,
        max_total_items: 128,
        ..DecodeLimits::TEST_FIXTURE
    };

    assert_eq!(
        decode_rlp_list(&input, limits),
        Err(DecodeError::NestingTooDeep)
    );
    Ok(())
}

#[test]
fn enforces_list_total_item_budget() {
    let limits = DecodeLimits {
        max_total_items: 1,
        ..DecodeLimits::TEST_FIXTURE
    };

    assert_eq!(
        decode_rlp_list(&[0xc1, 0x80], limits),
        Err(DecodeError::ItemCountExceeded)
    );
}

fn nested_empty_list(depth: usize) -> Result<Vec<u8>, DecodeError> {
    let mut encoded = vec![0xc0];
    for _ in 1..depth {
        encoded = encode_list(encoded)?;
    }
    Ok(encoded)
}

fn encode_list(payload: Vec<u8>) -> Result<Vec<u8>, DecodeError> {
    if payload.len() <= 55 {
        let payload_len = u8::try_from(payload.len()).map_err(|_| DecodeError::LengthOverflow)?;
        let prefix = 0xc0_u8
            .checked_add(payload_len)
            .ok_or(DecodeError::LengthOverflow)?;
        let mut encoded = vec![prefix];
        encoded.extend_from_slice(&payload);
        return Ok(encoded);
    }

    let len_bytes = minimal_be_len(payload.len())?;
    let len_of_len = u8::try_from(len_bytes.len()).map_err(|_| DecodeError::LengthOverflow)?;
    let prefix = 0xf7_u8
        .checked_add(len_of_len)
        .ok_or(DecodeError::LengthOverflow)?;
    let mut encoded = vec![prefix];
    encoded.extend_from_slice(&len_bytes);
    encoded.extend_from_slice(&payload);
    Ok(encoded)
}

fn minimal_be_len(mut len: usize) -> Result<Vec<u8>, DecodeError> {
    let mut out = Vec::new();
    while len > 0 {
        let byte = u8::try_from(len & 0xff).map_err(|_| DecodeError::LengthOverflow)?;
        out.push(byte);
        len >>= 8;
    }
    out.reverse();
    Ok(out)
}