eth-valkyoth-protocol 0.24.0

Fork-aware no_std Ethereum protocol validation state.
Documentation
use eth_valkyoth_codec::DecodeLimits;
use eth_valkyoth_primitives::{BlockNumber, Gas, Nonce, UnixTimestamp};
use std::vec::Vec;

use super::*;
use crate::{ForkActivation, ForkSpec};

#[path = "validity_test_fixtures.rs"]
mod fixtures;
use fixtures::*;

const TEST_LIMITS: DecodeLimits = DecodeLimits {
    max_input_bytes: 1024,
    max_list_items: 32,
    max_nesting_depth: 8,
    max_total_allocation: 1024,
    max_proof_nodes: b"node".len(),
    max_total_items: 128,
};

#[test]
fn set_code_validity_accepts_reviewed_context() {
    let authorizations = [authorization_tuple(
        &expected_chain_id_payload(),
        &starting_nonce_payload(),
    )];
    let transaction = decoded_transaction(&authorizations);
    assert!(transaction.is_ok(), "{transaction:?}");
    let Ok(transaction) = transaction else {
        return;
    };
    let authorities = [authority(0)];
    let accounts = [account(starting_nonce(), SetCodeAuthorityCode::Empty)];

    let result = validate_set_code_transaction_context(
        transaction,
        validity_context(None),
        &authorities[..],
        &accounts[..],
    );

    assert!(result.is_ok(), "{result:?}");
    if let Ok(valid) = result {
        assert_eq!(valid.authorization_count(), 1);
        assert_eq!(valid.applied_authorization_count(), 1);
        assert_eq!(valid.skipped_authorization_count(), 0);
        assert_eq!(valid.transaction().chain_id, expected_chain_id());
    }
}

#[test]
fn set_code_validity_rejects_empty_authorization_list() {
    assert_eq!(
        validate_single(&[]),
        Err(SetCodeTransactionValidityError::EmptyAuthorizationList)
    );
}

#[test]
fn set_code_validity_skips_wrong_authorization_chain() {
    let result = validate_single(&[
        authorization_tuple(&unexpected_chain_id_payload(), &starting_nonce_payload()),
        authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload()),
    ]);

    assert!(result.is_ok(), "{result:?}");
    if let Ok(valid) = result {
        assert_eq!(valid.authorization_count(), 2);
        assert_eq!(valid.applied_authorization_count(), 1);
        assert_eq!(valid.skipped_authorization_count(), 1);
    }
}

#[test]
fn set_code_validity_skips_max_authorization_nonce() {
    let result = validate_single(&[
        authorization_tuple(&expected_chain_id_payload(), &max_nonce_payload()),
        authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload()),
    ]);

    assert!(result.is_ok(), "{result:?}");
    if let Ok(valid) = result {
        assert_eq!(valid.authorization_count(), 2);
        assert_eq!(valid.applied_authorization_count(), 1);
        assert_eq!(valid.skipped_authorization_count(), 1);
    }
}

#[test]
fn set_code_validity_rejects_inactive_or_pre_prague_fork() {
    let authorizations = [authorization_tuple(
        &expected_chain_id_payload(),
        &starting_nonce_payload(),
    )];
    let transaction = decoded_transaction(&authorizations);
    assert!(transaction.is_ok(), "{transaction:?}");
    let Ok(transaction) = transaction else {
        return;
    };
    let authorities = [authority(0)];
    let accounts = [account(starting_nonce(), SetCodeAuthorityCode::Empty)];

    assert_eq!(
        validate_set_code_transaction_context(
            transaction,
            inactive_context(),
            &authorities[..],
            &accounts[..],
        ),
        Err(SetCodeTransactionValidityError::InactiveFork)
    );
    assert_eq!(
        validate_set_code_transaction_context(
            transaction,
            pre_prague_context(),
            &authorities[..],
            &accounts[..],
        ),
        Err(SetCodeTransactionValidityError::ForkBeforePrague)
    );
}

#[test]
fn set_code_validity_rejects_bad_fee_and_gas() {
    assert_eq!(
        validate_transaction(
            decoded_transaction_with_fee_and_gas(
                &priority_fee_too_high_payload(),
                &max_fee_payload(),
                &gas_limit_payload(),
            ),
            validity_context(None),
            starting_nonce(),
            SetCodeAuthorityCode::Empty,
        ),
        Err(SetCodeTransactionValidityError::PriorityFeeExceedsMaxFee)
    );
    assert_eq!(
        validate_transaction(
            decoded_transaction_with_fee_and_gas(
                &low_priority_fee_payload(),
                &max_fee_payload(),
                &gas_limit_payload(),
            ),
            validity_context(Some(Gas::new(21_001))),
            starting_nonce(),
            SetCodeAuthorityCode::Empty,
        ),
        Err(SetCodeTransactionValidityError::GasLimitTooLow)
    );
}

#[test]
fn set_code_validity_skips_bad_account_state() {
    let stale_nonce = validate_transaction(
        decoded_transaction(&[
            authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload()),
            authorization_tuple(&expected_chain_id_payload(), &next_nonce_payload()),
        ]),
        validity_context(None),
        next_nonce(),
        SetCodeAuthorityCode::Empty,
    );
    assert!(stale_nonce.is_ok(), "{stale_nonce:?}");
    if let Ok(valid) = stale_nonce {
        assert_eq!(valid.authorization_count(), 2);
        assert_eq!(valid.applied_authorization_count(), 1);
        assert_eq!(valid.skipped_authorization_count(), 1);
    }

    let invalid_code = validate_transaction(
        decoded_transaction(&[
            authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload()),
            authorization_tuple(&expected_chain_id_payload(), &next_nonce_payload()),
        ]),
        validity_context(None),
        next_nonce(),
        SetCodeAuthorityCode::Other,
    );
    assert!(invalid_code.is_ok(), "{invalid_code:?}");
    if let Ok(valid) = invalid_code {
        assert_eq!(valid.authorization_count(), 2);
        assert_eq!(valid.applied_authorization_count(), 0);
        assert_eq!(valid.skipped_authorization_count(), 2);
    }
}

#[test]
fn set_code_validity_tracks_repeated_authority_nonce_in_order() {
    let duplicate_nonce = validate_transaction(
        decoded_transaction(&[
            authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload()),
            authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload()),
        ]),
        validity_context(None),
        starting_nonce(),
        SetCodeAuthorityCode::Empty,
    );
    assert!(duplicate_nonce.is_ok(), "{duplicate_nonce:?}");
    if let Ok(valid) = duplicate_nonce {
        assert_eq!(valid.authorization_count(), 2);
        assert_eq!(valid.applied_authorization_count(), 1);
        assert_eq!(valid.skipped_authorization_count(), 1);
    }

    let sequential_nonce = validate_transaction(
        decoded_transaction(&[
            authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload()),
            authorization_tuple(&expected_chain_id_payload(), &next_nonce_payload()),
        ]),
        validity_context(None),
        starting_nonce(),
        SetCodeAuthorityCode::Empty,
    );
    assert!(sequential_nonce.is_ok(), "{sequential_nonce:?}");
    if let Ok(valid) = sequential_nonce {
        assert_eq!(valid.authorization_count(), 2);
        assert_eq!(valid.applied_authorization_count(), 2);
        assert_eq!(valid.skipped_authorization_count(), 0);
    }
}

#[test]
fn set_code_validity_accepts_synthesized_absent_account_state() {
    let authorizations = [authorization_tuple(
        &expected_chain_id_payload(),
        &uninitialized_nonce_payload(),
    )];
    let transaction = decoded_transaction(&authorizations);
    assert!(transaction.is_ok(), "{transaction:?}");
    let Ok(transaction) = transaction else {
        return;
    };
    let authorities = [authority(0)];
    let accounts = [account(uninitialized_nonce(), SetCodeAuthorityCode::Empty)];

    let result = validate_set_code_transaction_context(
        transaction,
        validity_context(None),
        &authorities[..],
        &accounts[..],
    );

    assert!(result.is_ok(), "{result:?}");
    if let Ok(valid) = result {
        assert_eq!(valid.applied_authorization_count(), 1);
        assert_eq!(valid.skipped_authorization_count(), 0);
    }
}

#[test]
fn authorization_chain_id_matches_universal_or_expected_chain() {
    let universal = authorization_tuple(&universal_chain_id_payload(), &starting_nonce_payload());
    let expected = authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload());

    assert!(validate_single(&[universal]).is_ok());
    assert!(validate_single(&[expected]).is_ok());
}

fn validate_single(
    authorizations: &[Vec<u8>],
) -> Result<ValidSetCodeTransaction<'static>, SetCodeTransactionValidityError> {
    validate_transaction(
        decoded_transaction(authorizations),
        validity_context(None),
        starting_nonce(),
        SetCodeAuthorityCode::Empty,
    )
}

fn validate_transaction(
    transaction: Result<UnvalidatedSetCodeTransaction<'static>, SetCodeTransactionDecodeError>,
    context: SetCodeTransactionValidationContext,
    nonce: Nonce,
    code: SetCodeAuthorityCode,
) -> Result<ValidSetCodeTransaction<'static>, SetCodeTransactionValidityError> {
    assert!(transaction.is_ok(), "{transaction:?}");
    let Ok(transaction) = transaction else {
        return Err(SetCodeTransactionValidityError::EmptyAuthorizationList);
    };
    let authorities = authorities_for(transaction);
    let accounts = [account(nonce, code)];
    validate_set_code_transaction_context(transaction, context, &authorities[..], &accounts[..])
}

fn authorities_for(
    transaction: UnvalidatedSetCodeTransaction<'_>,
) -> Vec<SetCodeAuthorizationAuthority> {
    (0..transaction.authorization_list.len())
        .map(authority)
        .collect()
}

fn decoded_transaction(
    authorizations: &[Vec<u8>],
) -> Result<UnvalidatedSetCodeTransaction<'static>, SetCodeTransactionDecodeError> {
    let tx = set_code_tx(
        &expected_chain_id_payload(),
        &default_priority_fee_payload(),
        &default_max_fee_payload(),
        &gas_limit_payload(),
        authorizations,
    );
    let leaked = Vec::leak(tx);
    crate::decode_set_code_transaction(leaked, TEST_LIMITS)
}

fn decoded_transaction_with_fee_and_gas(
    priority_fee: &[u8],
    max_fee: &[u8],
    gas_limit: &[u8],
) -> Result<UnvalidatedSetCodeTransaction<'static>, SetCodeTransactionDecodeError> {
    let auth = authorization_tuple(&expected_chain_id_payload(), &starting_nonce_payload());
    let tx = set_code_tx(
        &expected_chain_id_payload(),
        priority_fee,
        max_fee,
        gas_limit,
        &[auth],
    );
    let leaked = Vec::leak(tx);
    crate::decode_set_code_transaction(leaked, TEST_LIMITS)
}

fn validity_context(minimum_gas_limit: Option<Gas>) -> SetCodeTransactionValidationContext {
    SetCodeTransactionValidationContext {
        fork: ValidationContext {
            fork: ForkSpec {
                chain_id: expected_chain_id(),
                hardfork: Hardfork::Prague,
                activation: ForkActivation::BlockAndTimestamp {
                    activation_block: BlockNumber::new(active_block_number()),
                    activation_timestamp: UnixTimestamp::new(active_timestamp()),
                },
            },
            block_number: BlockNumber::new(active_block_number()),
            timestamp: UnixTimestamp::new(active_timestamp()),
        },
        minimum_gas_limit,
    }
}

fn inactive_context() -> SetCodeTransactionValidationContext {
    let mut context = validity_context(None);
    context.fork.block_number = BlockNumber::new(inactive_block_number());
    context
}

fn pre_prague_context() -> SetCodeTransactionValidationContext {
    let mut context = validity_context(None);
    context.fork.fork.hardfork = Hardfork::Cancun;
    context
}

fn authority(authorization_index: usize) -> SetCodeAuthorizationAuthority {
    SetCodeAuthorizationAuthority {
        authorization_index,
        authority: authority_address(),
    }
}

fn account(nonce: Nonce, code: SetCodeAuthorityCode) -> SetCodeAuthorityAccount {
    SetCodeAuthorityAccount {
        authority: authority_address(),
        nonce,
        code,
    }
}