eth-valkyoth-protocol 0.20.0

Fork-aware no_std Ethereum protocol validation state.
Documentation
use super::*;
use eth_valkyoth_codec::{DecodeError, DecodeLimits};
use eth_valkyoth_primitives::{Address, Gas, Nonce, Wei};
use std::vec::Vec;

#[test]
fn decodes_set_code_transaction_as_unvalidated() {
    let to = test_to_address();
    let auth_address = test_authorization_address();
    let auth_nonce = test_authorization_nonce();
    let auth = authorization_tuple(
        &[],
        &auth_address,
        auth_nonce,
        valid_authorization_y_parity(),
    );
    let tx = set_code_tx(&[1], &to, &[auth.as_slice()], 1);
    let result = decode_set_code_transaction(&tx, TEST_LIMITS);
    assert!(result.is_ok());

    if let Ok(tx) = result {
        assert_eq!(tx.chain_id.get(), 1);
        assert_eq!(tx.nonce, Nonce::new(2));
        assert_eq!(tx.max_priority_fee_per_gas, Wei::from_u128(3));
        assert_eq!(tx.max_fee_per_gas, Wei::from_u128(4));
        assert_eq!(tx.gas_limit, Gas::new(21_000));
        assert_eq!(tx.to, Address::from_bytes(to));
        assert_eq!(tx.value, Wei::from_u128(5));
        assert_eq!(tx.input, &[]);
        assert_eq!(tx.access_list.address_count(), 0);
        assert_eq!(tx.authorization_list.len(), 1);
        assert_eq!(tx.y_parity, SignatureYParity::Odd);
        assert_eq!(last_byte(tx.r), Some(1));
        assert_eq!(last_byte(tx.s), Some(2));

        let mut authorizations = named_authorization_items(tx.authorization_list.authorizations());
        let first = authorizations.next();
        assert!(matches!(first, Some(Ok(_))));
        if let Some(Ok(auth)) = first {
            assert!(auth.chain_id.is_universal());
            assert_eq!(auth.address, Address::from_bytes(auth_address));
            assert_eq!(auth.nonce, Nonce::new(u64::from(auth_nonce)));
            assert_eq!(auth.y_parity, SignatureYParity::Odd);
            assert_eq!(last_byte(auth.r), Some(1));
            assert_eq!(last_byte(auth.s), Some(2));
        }
        assert_eq!(authorizations.next(), None);
    }
}

#[test]
fn set_code_decoder_defers_empty_authorization_list_validation() {
    let to = test_to_address();
    let authorizations: [&[u8]; 0] = [];
    let tx = set_code_tx(&[1], &to, &authorizations, 1);
    let result = decode_set_code_transaction(&tx, TEST_LIMITS);
    assert!(result.is_ok());

    if let Ok(tx) = result {
        assert!(tx.authorization_list.is_empty());
    }
}

#[test]
fn round_trips_set_code_transaction_encoding() {
    let to = test_to_address();
    let auth_address = test_authorization_address();
    let auth = authorization_tuple(
        &[1],
        &auth_address,
        test_authorization_nonce(),
        valid_authorization_y_parity(),
    );
    let tx = set_code_tx(&[1], &to, &[auth.as_slice()], 1);
    let decoded = decode_set_code_transaction(&tx, TEST_LIMITS);
    assert!(decoded.is_ok(), "{decoded:?}");

    if let Ok(transaction) = decoded {
        let mut output = [0_u8; 128];
        assert_eq!(encoded_set_code_transaction_len(&transaction), Ok(tx.len()));
        let written = encode_set_code_transaction(&transaction, &mut output);
        assert_eq!(written, Ok(tx.len()));
        assert_eq!(output.get(..tx.len()), Some(tx.as_slice()));

        let unified = UnvalidatedTransaction::SetCode(transaction);
        assert_eq!(encoded_transaction_len(unified), Ok(tx.len()));
        let written = encode_transaction(unified, &mut output);
        assert_eq!(written, Ok(tx.len()));
        assert_eq!(output.get(..tx.len()), Some(tx.as_slice()));
    }
}

#[test]
fn rejects_non_set_code_transaction_type() {
    assert_eq!(
        decode_set_code_transaction(&[0x03, 0xc0], TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::WrongTransactionType { type_byte: 3 })
    );
    assert_eq!(
        decode_set_code_transaction(CREATE_TX, TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::WrongTransactionType { type_byte: 0 })
    );
}

#[test]
fn rejects_wrong_set_code_field_count() {
    assert_eq!(
        decode_set_code_transaction(&[0x04, 0xc0], TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::WrongFieldCount {
            expected: SET_CODE_TRANSACTION_FIELD_COUNT,
            found: 0,
        })
    );
}

#[test]
fn rejects_malformed_set_code_payload_as_payload_field() {
    assert_eq!(
        decode_set_code_transaction(&[0x04, 0x80], TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::FieldDecode {
            field: SetCodeTransactionField::Payload,
            source: DecodeError::UnexpectedScalar,
        })
    );
}

#[test]
fn rejects_reserved_set_code_chain_id_zero() {
    let to = test_to_address();
    let auth_address = test_authorization_address();
    let auth = authorization_tuple(
        &[],
        &auth_address,
        test_authorization_nonce(),
        valid_authorization_y_parity(),
    );
    let tx = set_code_tx(&[], &to, &[auth.as_slice()], 1);

    assert_eq!(
        decode_set_code_transaction(&tx, TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::FieldDecode {
            field: SetCodeTransactionField::ChainId,
            source: DecodeError::Malformed,
        })
    );
}

#[test]
fn rejects_set_code_create_target() {
    let auth_address = test_authorization_address();
    let auth = authorization_tuple(
        &[],
        &auth_address,
        test_authorization_nonce(),
        valid_authorization_y_parity(),
    );
    let tx = set_code_tx(&[1], &[], &[auth.as_slice()], 1);

    assert_eq!(
        decode_set_code_transaction(&tx, TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::InvalidToLength { found: 0 })
    );
}

#[test]
fn rejects_invalid_authorization_tuple_shape() {
    let to = test_to_address();
    let malformed_auth = [0xc0];
    let tx = set_code_tx(&[1], &to, &[malformed_auth.as_slice()], 1);

    assert_eq!(
        decode_set_code_transaction(&tx, TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::InvalidAuthorizationFieldCount { found: 0 })
    );
}

#[test]
fn reports_invalid_authorization_subfield() {
    let to = test_to_address();
    let auth_address = test_authorization_address();
    let auth = authorization_tuple(
        &[0, 1],
        &auth_address,
        test_authorization_nonce(),
        valid_authorization_y_parity(),
    );
    let tx = set_code_tx(&[1], &to, &[auth.as_slice()], 1);

    assert_eq!(
        decode_set_code_transaction(&tx, TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::AuthorizationFieldDecode {
            field: SetCodeAuthorizationField::ChainId,
            source: DecodeError::Malformed,
        })
    );
}

#[test]
fn rejects_invalid_authorization_address_length() {
    let to = test_to_address();
    let short_address = short_test_address();
    let auth = authorization_tuple(
        &[],
        &short_address,
        test_authorization_nonce(),
        valid_authorization_y_parity(),
    );
    let tx = set_code_tx(&[1], &to, &[auth.as_slice()], 1);

    assert_eq!(
        decode_set_code_transaction(&tx, TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::InvalidAuthorizationAddressLength { found: 1 })
    );
}

#[test]
fn rejects_invalid_authorization_y_parity() {
    let to = test_to_address();
    let auth_address = test_authorization_address();
    let auth = authorization_tuple(
        &[],
        &auth_address,
        test_authorization_nonce(),
        invalid_authorization_y_parity(),
    );
    let tx = set_code_tx(&[1], &to, &[auth.as_slice()], 1);

    assert_eq!(
        decode_set_code_transaction(&tx, TEST_LIMITS),
        Err(SetCodeTransactionDecodeError::InvalidAuthorizationYParity { value: 2 })
    );
}

#[test]
fn rejects_oversized_authorization_list() {
    let to = test_to_address();
    let auth_address = test_authorization_address();
    let auth = authorization_tuple(
        &[],
        &auth_address,
        test_authorization_nonce(),
        valid_authorization_y_parity(),
    );
    let authorizations = [auth.as_slice(); 17];
    let tx = set_code_tx(&[1], &to, &authorizations, 1);
    let limits = DecodeLimits {
        max_input_bytes: 1024,
        max_list_items: 13,
        max_nesting_depth: 8,
        max_total_allocation: 1024,
        max_proof_nodes: 4,
        max_total_items: 128,
    };

    assert!(decode_set_code_transaction(&tx, limits).is_err());
}

fn set_code_tx<T: AsRef<[u8]>>(
    chain_id: &[u8],
    to: &[u8],
    authorizations: &[T],
    y_parity: u8,
) -> Vec<u8> {
    let mut fields = Vec::new();
    push_scalar(&mut fields, chain_id);
    push_scalar(&mut fields, &[2]);
    push_scalar(&mut fields, &[3]);
    push_scalar(&mut fields, &[4]);
    push_scalar(&mut fields, &[0x52, 0x08]);
    push_scalar(&mut fields, to);
    push_scalar(&mut fields, &[5]);
    push_scalar(&mut fields, &[]);
    push_list(&mut fields, &[]);

    let mut auth_list = Vec::new();
    for authorization in authorizations {
        auth_list.extend_from_slice(authorization.as_ref());
    }
    push_list(&mut fields, &auth_list);

    push_scalar(&mut fields, &[y_parity]);
    push_scalar(&mut fields, &[1]);
    push_scalar(&mut fields, &[2]);

    let mut tx = Vec::new();
    tx.push(SET_CODE_TRANSACTION_TYPE);
    push_list(&mut tx, &fields);
    tx
}

fn authorization_tuple(chain_id: &[u8], address: &[u8], nonce: u8, y_parity: u8) -> Vec<u8> {
    let mut fields = Vec::new();
    push_scalar(&mut fields, chain_id);
    push_scalar(&mut fields, address);
    push_scalar(&mut fields, &[nonce]);
    push_scalar(&mut fields, &[y_parity]);
    push_scalar(&mut fields, &[1]);
    push_scalar(&mut fields, &[2]);

    let mut tuple = Vec::new();
    push_list(&mut tuple, &fields);
    tuple
}

fn test_to_address() -> [u8; 20] {
    test_address_from_label(b"set-code-to")
}

fn test_authorization_address() -> [u8; 20] {
    test_address_from_label(b"set-code-auth")
}

fn test_address_from_label(label: &[u8]) -> [u8; 20] {
    let mut bytes = [0_u8; 20];
    if label.is_empty() {
        return bytes;
    }
    for (index, byte) in bytes.iter_mut().enumerate() {
        let Some(label_index) = index.checked_rem(label.len()) else {
            return bytes;
        };
        let label_byte = label.get(label_index).copied().unwrap_or_default();
        *byte = label_byte.wrapping_add(u8::try_from(index).unwrap_or_default());
    }
    bytes
}

fn short_test_address() -> Vec<u8> {
    let bytes = test_address_from_label(b"short-auth");
    let mut short = Vec::new();
    if let Some(byte) = bytes.first() {
        short.push(*byte);
    }
    short
}

fn test_authorization_nonce() -> u8 {
    u8::try_from("nonce".len()).unwrap_or_default()
}

fn valid_authorization_y_parity() -> u8 {
    u8::from(true)
}

fn invalid_authorization_y_parity() -> u8 {
    valid_authorization_y_parity().saturating_add(u8::from(true))
}

fn push_scalar(out: &mut Vec<u8>, payload: &[u8]) {
    if let Some(byte) = payload
        .first()
        .copied()
        .filter(|byte| payload.len() == 1 && *byte < 0x80)
    {
        out.push(byte);
    } else {
        push_prefixed(out, 0x80, payload);
    }
}

fn push_list(out: &mut Vec<u8>, payload: &[u8]) {
    push_prefixed(out, 0xc0, payload);
}

fn push_prefixed(out: &mut Vec<u8>, short_base: u8, payload: &[u8]) {
    if payload.len() <= 55 {
        let Ok(length) = u8::try_from(payload.len()) else {
            return;
        };
        let Some(prefix) = short_base.checked_add(length) else {
            return;
        };
        out.push(prefix);
    } else {
        let Some(prefix) = short_base.checked_add(56) else {
            return;
        };
        let Ok(length) = u8::try_from(payload.len()) else {
            return;
        };
        out.push(prefix);
        out.push(length);
    }
    out.extend_from_slice(payload);
}

fn named_authorization_items<'a>(
    items: crate::SetCodeAuthorizationItems<'a>,
) -> crate::SetCodeAuthorizationItems<'a> {
    items
}