mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-25 07:03:13 +00:00
test: bridge guard account flags modification
This commit is contained in:
parent
d1637b65e2
commit
4832cfbc9a
@ -30,6 +30,8 @@ pub mod program_deployment_transaction;
|
|||||||
pub mod public_transaction;
|
pub mod public_transaction;
|
||||||
mod signature;
|
mod signature;
|
||||||
mod state;
|
mod state;
|
||||||
|
#[cfg(feature = "test-utils")]
|
||||||
|
pub mod test_utils;
|
||||||
mod validated_state_diff;
|
mod validated_state_diff;
|
||||||
|
|
||||||
mod privacy_preserving_circuit {
|
mod privacy_preserving_circuit {
|
||||||
|
|||||||
28
lee/state_machine/src/test_utils.rs
Normal file
28
lee/state_machine/src/test_utils.rs
Normal file
@ -0,0 +1,28 @@
|
|||||||
|
//! Test-only constructors for otherwise-opaque state types.
|
||||||
|
//!
|
||||||
|
//! A [`ValidatedStateDiff`] can normally only be produced by the transaction validation
|
||||||
|
//! functions, which guarantees it has been checked before any state mutation. These
|
||||||
|
//! helpers let downstream crates unit-test *post-execution* validation logic — e.g. the
|
||||||
|
//! system-account and bridge guards in `common` — against a hand-built diff, without
|
||||||
|
//! running a program in the zkVM.
|
||||||
|
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use crate::{
|
||||||
|
Account, AccountId,
|
||||||
|
validated_state_diff::{StateDiff, ValidatedStateDiff},
|
||||||
|
};
|
||||||
|
|
||||||
|
/// Builds a [`ValidatedStateDiff`] carrying only the given public-account changes.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn validated_state_diff_from_public_diff(
|
||||||
|
public_diff: HashMap<AccountId, Account>,
|
||||||
|
) -> ValidatedStateDiff {
|
||||||
|
ValidatedStateDiff::new_unchecked(StateDiff {
|
||||||
|
signer_account_ids: Vec::new(),
|
||||||
|
public_diff,
|
||||||
|
new_commitments: Vec::new(),
|
||||||
|
new_nullifiers: Vec::new(),
|
||||||
|
program: None,
|
||||||
|
})
|
||||||
|
}
|
||||||
@ -35,10 +35,24 @@ pub struct StateDiff {
|
|||||||
|
|
||||||
/// The validated output of executing or verifying a transaction, ready to be applied to the state.
|
/// The validated output of executing or verifying a transaction, ready to be applied to the state.
|
||||||
///
|
///
|
||||||
/// Can only be constructed by the transaction validation functions inside this crate, ensuring the
|
/// It can only be constructed by the transaction validation functions inside this crate, ensuring
|
||||||
/// diff has been checked before any state mutation occurs.
|
/// the diff has been checked before any state mutation occurs. Under the `test-utils` feature the
|
||||||
|
/// [`crate::test_utils`] module additionally exposes a hand-rolled constructor for unit-testing
|
||||||
|
/// downstream validation logic; that feature must never be enabled in a production build.
|
||||||
pub struct ValidatedStateDiff(StateDiff);
|
pub struct ValidatedStateDiff(StateDiff);
|
||||||
|
|
||||||
|
#[cfg(feature = "test-utils")]
|
||||||
|
impl ValidatedStateDiff {
|
||||||
|
/// Test-only constructor that wraps an already-built [`StateDiff`] **without validating it**.
|
||||||
|
///
|
||||||
|
/// Kept in this module so the wrapped field can stay private: in a normal build (feature off)
|
||||||
|
/// the only ways to obtain a `ValidatedStateDiff` remain the `from_*_transaction` validators.
|
||||||
|
#[must_use]
|
||||||
|
pub const fn new_unchecked(state_diff: StateDiff) -> Self {
|
||||||
|
Self(state_diff)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ValidatedStateDiff {
|
impl ValidatedStateDiff {
|
||||||
pub fn from_public_transaction(
|
pub fn from_public_transaction(
|
||||||
tx: &PublicTransaction,
|
tx: &PublicTransaction,
|
||||||
|
|||||||
@ -25,3 +25,6 @@ log.workspace = true
|
|||||||
hex.workspace = true
|
hex.workspace = true
|
||||||
borsh.workspace = true
|
borsh.workspace = true
|
||||||
logos-blockchain-common-http-client.workspace = true
|
logos-blockchain-common-http-client.workspace = true
|
||||||
|
|
||||||
|
[dev-dependencies]
|
||||||
|
lee = { workspace = true, features = ["test-utils"] }
|
||||||
|
|||||||
@ -1,4 +1,12 @@
|
|||||||
|
// Backs the hand-built state/diff helpers below, which are compiled only for `common`'s own
|
||||||
|
// unit tests. They rely on `lee::test_utils`, gated behind `lee`'s `test-utils` feature and
|
||||||
|
// enabled here via dev-dependencies, so it never reaches a production build.
|
||||||
|
#[cfg(test)]
|
||||||
|
use std::collections::HashMap;
|
||||||
|
|
||||||
use lee::AccountId;
|
use lee::AccountId;
|
||||||
|
#[cfg(test)]
|
||||||
|
use lee::{Account, PrivateKey, PublicKey, V03State, ValidatedStateDiff};
|
||||||
|
|
||||||
use crate::{
|
use crate::{
|
||||||
HashType,
|
HashType,
|
||||||
@ -13,6 +21,33 @@ pub fn sequencer_sign_key_for_testing() -> lee::PrivateKey {
|
|||||||
lee::PrivateKey::try_new([37; 32]).unwrap()
|
lee::PrivateKey::try_new([37; 32]).unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A syntactically valid `Public` transaction. Its contents are irrelevant to the
|
||||||
|
/// bridge guard, which only branches on the transaction *variant* and the diff.
|
||||||
|
#[cfg(test)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn any_public_transaction() -> LeeTransaction {
|
||||||
|
let sender_key = PrivateKey::try_new([9_u8; 32]).expect("valid key");
|
||||||
|
let sender_id = AccountId::from(&PublicKey::new_from_private_key(&sender_key));
|
||||||
|
let recipient_key = PrivateKey::try_new([8_u8; 32]).expect("valid key");
|
||||||
|
let recipient_id = AccountId::from(&PublicKey::new_from_private_key(&recipient_key));
|
||||||
|
create_transaction_native_token_transfer(sender_id, 0, recipient_id, 1, &sender_key)
|
||||||
|
}
|
||||||
|
|
||||||
|
/// Builds a state whose only entry is `account_id` (set to `pre`) and a single-entry diff
|
||||||
|
/// that maps `account_id` to `post`, so the validation guards can be exercised in isolation.
|
||||||
|
#[cfg(test)]
|
||||||
|
#[must_use]
|
||||||
|
pub fn state_and_diff(
|
||||||
|
account_id: AccountId,
|
||||||
|
pre: Account,
|
||||||
|
post: Account,
|
||||||
|
) -> (V03State, ValidatedStateDiff) {
|
||||||
|
let state = V03State::new().with_public_accounts([(account_id, pre)]);
|
||||||
|
let diff =
|
||||||
|
lee::test_utils::validated_state_diff_from_public_diff(HashMap::from([(account_id, post)]));
|
||||||
|
(state, diff)
|
||||||
|
}
|
||||||
|
|
||||||
// Dummy producers
|
// Dummy producers
|
||||||
|
|
||||||
/// Produce dummy block with provided transactions + clock transaction an the end.
|
/// Produce dummy block with provided transactions + clock transaction an the end.
|
||||||
|
|||||||
@ -260,9 +260,112 @@ fn validate_doesnt_modify_account(
|
|||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use lee::{AccountId, PrivateKey, PublicKey, V03State};
|
use lee::{Account, AccountId, PrivateKey, PublicKey, V03State};
|
||||||
|
use lee_core::account::Nonce;
|
||||||
|
|
||||||
use crate::test_utils::create_transaction_native_token_transfer;
|
use super::validate_doesnt_modify_account;
|
||||||
|
use crate::test_utils::{
|
||||||
|
any_public_transaction, create_transaction_native_token_transfer, state_and_diff,
|
||||||
|
};
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bridge_guard_allows_balance_only_increase() {
|
||||||
|
// A diff that *only* increases the bridge balance (the legitimate deposit shape)
|
||||||
|
// must be accepted.
|
||||||
|
let bridge_id = system_accounts::bridge_account_id();
|
||||||
|
let pre = Account {
|
||||||
|
balance: 500,
|
||||||
|
nonce: Nonce(7),
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
let post = Account {
|
||||||
|
balance: 600,
|
||||||
|
..pre.clone()
|
||||||
|
};
|
||||||
|
let (state, diff) = state_and_diff(bridge_id, pre, post);
|
||||||
|
|
||||||
|
let tx = any_public_transaction();
|
||||||
|
assert!(
|
||||||
|
tx.validate_bridge_account_modification(&state, &diff)
|
||||||
|
.is_ok(),
|
||||||
|
"a balance-only increase of the bridge account must be allowed",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bridge_guard_rejects_data_modification_even_when_balance_increases() {
|
||||||
|
// A diff that changes the bridge account's data (here: the nonce) while *also*
|
||||||
|
// increasing its balance must be rejected.
|
||||||
|
let bridge_id = system_accounts::bridge_account_id();
|
||||||
|
let pre = Account {
|
||||||
|
balance: 500,
|
||||||
|
nonce: Nonce(7),
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
let post = Account {
|
||||||
|
balance: 600,
|
||||||
|
nonce: Nonce(8),
|
||||||
|
..pre.clone()
|
||||||
|
};
|
||||||
|
let (state, diff) = state_and_diff(bridge_id, pre, post);
|
||||||
|
|
||||||
|
let tx = any_public_transaction();
|
||||||
|
assert!(
|
||||||
|
tx.validate_bridge_account_modification(&state, &diff)
|
||||||
|
.is_err(),
|
||||||
|
"modifying bridge account data must be rejected even if the balance increases",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn bridge_guard_rejects_zero_value_deposit() {
|
||||||
|
// A diff that touches the bridge account without *strictly* increasing its balance
|
||||||
|
// must be rejected — a zero-value deposit is not a real credit.
|
||||||
|
let bridge_id = system_accounts::bridge_account_id();
|
||||||
|
let pre = Account {
|
||||||
|
balance: 500,
|
||||||
|
nonce: Nonce(7),
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
let post = pre.clone();
|
||||||
|
let (state, diff) = state_and_diff(bridge_id, pre, post);
|
||||||
|
|
||||||
|
let tx = any_public_transaction();
|
||||||
|
assert!(
|
||||||
|
tx.validate_bridge_account_modification(&state, &diff)
|
||||||
|
.is_err(),
|
||||||
|
"a bridge diff that does not strictly increase the balance must be rejected",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn validate_doesnt_modify_account_flags_a_changed_account() {
|
||||||
|
// Directly exercise the system-account guard with a diff that genuinely changes a
|
||||||
|
// clock account, then with one that leaves it untouched. The inverted comparison would
|
||||||
|
// treat a changed account as unchanged and wave it through (and would flag an *unchanged*
|
||||||
|
// account instead).
|
||||||
|
let clock_id = system_accounts::clock_account_ids()[0];
|
||||||
|
let pre = Account {
|
||||||
|
balance: 1_000,
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let changed = Account {
|
||||||
|
balance: 2_000,
|
||||||
|
..Account::default()
|
||||||
|
};
|
||||||
|
let (state, diff) = state_and_diff(clock_id, pre.clone(), changed);
|
||||||
|
assert!(
|
||||||
|
validate_doesnt_modify_account(&state, &diff, clock_id).is_err(),
|
||||||
|
"a diff that changes a system account must be rejected",
|
||||||
|
);
|
||||||
|
|
||||||
|
let (unchanged_state, unchanged_diff) = state_and_diff(clock_id, pre.clone(), pre);
|
||||||
|
assert!(
|
||||||
|
validate_doesnt_modify_account(&unchanged_state, &unchanged_diff, clock_id).is_ok(),
|
||||||
|
"a diff that leaves a system account unchanged must be accepted",
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn system_account_ids_are_distinct_and_non_default() {
|
fn system_account_ids_are_distinct_and_non_default() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user