mirror of
https://github.com/logos-blockchain/lssa.git
synced 2026-06-14 13:10:04 +00:00
test: address mutants discovered during fuzzing
This commit is contained in:
parent
c063872f1c
commit
759d4a849e
@ -210,6 +210,17 @@ mod tests {
|
||||
kind
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn proof_inner_roundtrip() {
|
||||
// `Proof::from_inner(b).into_inner()` must return exactly `b`. Catches
|
||||
// mutations of `into_inner` returning `vec![]`, `vec![0]`, or `vec![1]`,
|
||||
// and of `from_inner` discarding its argument.
|
||||
let bytes = vec![0xDE_u8, 0xAD, 0xBE, 0xEF];
|
||||
assert_eq!(Proof::from_inner(bytes.clone()).into_inner(), bytes);
|
||||
assert!(Proof::from_inner(vec![]).into_inner().is_empty());
|
||||
assert_eq!(Proof::from_inner(vec![0xFF]).into_inner(), vec![0xFF_u8]);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn prove_privacy_preserving_execution_circuit_public_and_private_pre_accounts() {
|
||||
let recipient_keys = test_private_account_keys_1();
|
||||
|
||||
@ -498,6 +498,20 @@ mod tests {
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn elf_returns_the_program_bytecode_constant() {
|
||||
// `Program::elf` must return exactly the compile-time ELF, never an empty
|
||||
// or placeholder slice. Catches mutations returning `Vec::leak(Vec::new())`,
|
||||
// `Vec::leak(vec![0])`, or `Vec::leak(vec![1])`.
|
||||
let at = Program::authenticated_transfer_program();
|
||||
assert!(!at.elf().is_empty());
|
||||
assert_eq!(at.elf(), AUTHENTICATED_TRANSFER_ELF);
|
||||
|
||||
let token = Program::token();
|
||||
assert!(!token.elf().is_empty());
|
||||
assert_eq!(token.elf(), TOKEN_ELF);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn program_execution() {
|
||||
let program = Program::simple_balance_transfer();
|
||||
|
||||
@ -16,3 +16,18 @@ impl Message {
|
||||
self.bytecode
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::Message;
|
||||
|
||||
#[test]
|
||||
fn bytecode_roundtrip() {
|
||||
// `Message::new(b).into_bytecode()` must return exactly `b`. Catches
|
||||
// mutations of `into_bytecode` returning `vec![]`, `vec![0]`, or `vec![1]`.
|
||||
let bytecode = vec![0x7F_u8, 0x45, 0x4C, 0x46]; // ELF magic
|
||||
assert_eq!(Message::new(bytecode.clone()).into_bytecode(), bytecode);
|
||||
assert!(Message::new(vec![]).into_bytecode().is_empty());
|
||||
assert_eq!(Message::new(vec![0xAB]).into_bytecode(), vec![0xAB_u8]);
|
||||
}
|
||||
}
|
||||
|
||||
@ -613,6 +613,62 @@ pub mod tests {
|
||||
PublicTransaction::new(message, witness_set)
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genesis_system_accounts_have_expected_contents() {
|
||||
// System-account IDs must be distinct and non-default, and the genesis
|
||||
// faucet/bridge accounts must carry their expected field values. Catches
|
||||
// mutations that replace `system_faucet_account`/`system_bridge_account`
|
||||
// with `Default::default()`, delete their `balance`/`program_owner`
|
||||
// fields, or replace `system_bridge_account_id` with `Default::default()`.
|
||||
let faucet_id = system_faucet_account_id();
|
||||
let bridge_id = system_bridge_account_id();
|
||||
assert_ne!(bridge_id, AccountId::default());
|
||||
assert_ne!(faucet_id, bridge_id);
|
||||
|
||||
let state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
let default_owner = Account::default().program_owner;
|
||||
|
||||
let faucet = state.get_account_by_id(faucet_id);
|
||||
assert_eq!(faucet.balance, u128::MAX, "faucet must hold u128::MAX");
|
||||
assert_ne!(
|
||||
faucet.program_owner, default_owner,
|
||||
"faucet must have a non-default program_owner"
|
||||
);
|
||||
|
||||
let bridge = state.get_account_by_id(bridge_id);
|
||||
assert_ne!(
|
||||
bridge.program_owner, default_owner,
|
||||
"bridge must have a non-default program_owner"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn genesis_commitment_set_digest_differs_from_empty_state() {
|
||||
// The genesis state inserts DUMMY_COMMITMENT, so its commitment-set digest
|
||||
// must differ from a freshly-created empty state's all-zero root. Catches
|
||||
// the mutation that replaces `commitment_set_digest` with `Default::default()`.
|
||||
let genesis = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
let empty = V03State::new();
|
||||
assert_ne!(
|
||||
genesis.commitment_set_digest(),
|
||||
empty.commitment_set_digest()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn add_pinata_token_program_sets_non_default_owner_and_data() {
|
||||
// The account created by `add_pinata_token_program` must have a non-default
|
||||
// `program_owner` and non-default `data`. Catches deletion of either field
|
||||
// from the struct literal.
|
||||
let id = AccountId::new([0xAB_u8; 32]);
|
||||
let mut state = V03State::new_with_genesis_accounts(&[], vec![], 0);
|
||||
state.add_pinata_token_program(id);
|
||||
let acc = state.get_account_by_id(id);
|
||||
let default = Account::default();
|
||||
assert_ne!(acc.program_owner, default.program_owner);
|
||||
assert_ne!(acc.data, default.data);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn new_with_genesis() {
|
||||
let key1 = PrivateKey::try_new([1; 32]).unwrap();
|
||||
|
||||
@ -526,6 +526,44 @@ mod tests {
|
||||
validated_state_diff::ValidatedStateDiff,
|
||||
};
|
||||
|
||||
#[test]
|
||||
fn public_diff_reflects_a_successful_transfer() {
|
||||
// A successful native transfer must record the debited sender in
|
||||
// `public_diff()`. Catches the mutation that replaces `public_diff` with
|
||||
// `HashMap::new()` (which would hide every account change).
|
||||
use authenticated_transfer_core::Instruction as AtInstruction;
|
||||
|
||||
let from_key = PrivateKey::try_new([1_u8; 32]).unwrap();
|
||||
let from = AccountId::from(&PublicKey::new_from_private_key(&from_key));
|
||||
let to_key = PrivateKey::try_new([2_u8; 32]).unwrap();
|
||||
let to = AccountId::from(&PublicKey::new_from_private_key(&to_key));
|
||||
|
||||
let state = V03State::new_with_genesis_accounts(&[(from, 100)], vec![], 0);
|
||||
let program_id = Program::authenticated_transfer_program().id();
|
||||
let message = Message::try_new(
|
||||
program_id,
|
||||
vec![from, to],
|
||||
vec![Nonce(0), Nonce(0)],
|
||||
AtInstruction::Transfer { amount: 5 },
|
||||
)
|
||||
.unwrap();
|
||||
let witness_set = WitnessSet::for_message(&message, &[&from_key, &to_key]);
|
||||
let tx = crate::PublicTransaction::new(message, witness_set);
|
||||
|
||||
let diff = ValidatedStateDiff::from_public_transaction(&tx, &state, 1, 0)
|
||||
.expect("a valid native transfer must validate");
|
||||
let public_diff = diff.public_diff();
|
||||
|
||||
assert!(
|
||||
public_diff.contains_key(&from),
|
||||
"public_diff must contain the debited sender",
|
||||
);
|
||||
assert_eq!(
|
||||
public_diff[&from].balance, 95,
|
||||
"sender balance in the diff must reflect the debit",
|
||||
);
|
||||
}
|
||||
|
||||
/// Privacy-path version of the authorization-injection attack. The test passes when the
|
||||
/// attack is rejected and the victim's balance is left untouched.
|
||||
///
|
||||
|
||||
@ -53,3 +53,33 @@ impl From<BasicAuth> for BasicAuthCredentials {
|
||||
Self::new(value.username, value.password)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use std::str::FromStr;
|
||||
|
||||
use super::BasicAuth;
|
||||
|
||||
#[test]
|
||||
fn parse_preserves_non_empty_password() {
|
||||
let auth = BasicAuth::from_str("user:secret").expect("must parse");
|
||||
assert_eq!(auth.username, "user");
|
||||
assert_eq!(auth.password.as_deref(), Some("secret"));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_empty_password_is_none() {
|
||||
// A trailing colon means an empty password, which must become `None`.
|
||||
// Catches deletion of `!` in `.filter(|p| !p.is_empty())`, which would
|
||||
// instead yield `Some("")`.
|
||||
let auth = BasicAuth::from_str("user:").expect("must parse");
|
||||
assert_eq!(auth.password, None);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_username_only_has_no_password() {
|
||||
let auth = BasicAuth::from_str("alice").expect("must parse");
|
||||
assert_eq!(auth.username, "alice");
|
||||
assert_eq!(auth.password, None);
|
||||
}
|
||||
}
|
||||
|
||||
@ -93,4 +93,16 @@ mod tests {
|
||||
let deserialized = HashType::from_str(&serialized).unwrap();
|
||||
assert_eq!(original, deserialized);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn as_ref_returns_exact_inner_bytes() {
|
||||
// `HashType::as_ref` must return exactly the inner `[u8; 32]` — not an
|
||||
// empty slice or a placeholder. Catches mutations of `as_ref` that return
|
||||
// `Vec::leak(Vec::new())`, `vec![0]`, or `vec![1]`.
|
||||
let known = [0x42_u8; 32];
|
||||
let hash = HashType(known);
|
||||
assert_eq!(hash.as_ref(), &known);
|
||||
assert_eq!(hash.as_ref().len(), 32);
|
||||
assert_eq!(HashType([0_u8; 32]).as_ref().len(), 32);
|
||||
}
|
||||
}
|
||||
|
||||
@ -188,3 +188,47 @@ fn validate_doesnt_modify_account(
|
||||
Ok(())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use lee::{
|
||||
AccountId, CLOCK_01_PROGRAM_ACCOUNT_ID, PrivateKey, PublicKey, V03State,
|
||||
system_bridge_account_id, system_faucet_account_id,
|
||||
};
|
||||
|
||||
use crate::test_utils::create_transaction_native_token_transfer;
|
||||
|
||||
#[test]
|
||||
fn system_account_ids_are_distinct_and_non_default() {
|
||||
let faucet = system_faucet_account_id();
|
||||
let bridge = system_bridge_account_id();
|
||||
assert_ne!(faucet, AccountId::default());
|
||||
assert_ne!(bridge, AccountId::default());
|
||||
assert_ne!(faucet, bridge);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validate_on_state_rejects_modifying_a_system_account() {
|
||||
// A native transfer that credits a clock system account *changes* that
|
||||
// account, so `validate_doesnt_modify_account` must reject it. Catches
|
||||
// the `!=` → `==` inversion at `validate_doesnt_modify_account` (a changed
|
||||
// account would no longer be flagged) and `public_diff → HashMap::new()`
|
||||
// (an empty diff hides the modification).
|
||||
let sender_key = PrivateKey::try_new([5_u8; 32]).expect("valid key");
|
||||
let sender_id = AccountId::from(&PublicKey::new_from_private_key(&sender_key));
|
||||
let state = V03State::new_with_genesis_accounts(&[(sender_id, 10_000)], vec![], 0);
|
||||
|
||||
let tx = create_transaction_native_token_transfer(
|
||||
sender_id,
|
||||
0,
|
||||
CLOCK_01_PROGRAM_ACCOUNT_ID,
|
||||
100,
|
||||
&sender_key,
|
||||
);
|
||||
|
||||
assert!(
|
||||
tx.validate_on_state(&state, 1, 0).is_err(),
|
||||
"validate_on_state must reject a transfer that credits a clock system account",
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user