mirror of
https://github.com/logos-blockchain/logos-execution-zone.git
synced 2026-07-09 15:29:34 +00:00
WIP on (no branch)
test: bridge guard account flags modification
This commit is contained in:
parent
d1637b65e2
commit
5eb26152d5
@ -30,6 +30,8 @@ pub mod program_deployment_transaction;
|
||||
pub mod public_transaction;
|
||||
mod signature;
|
||||
mod state;
|
||||
#[cfg(feature = "test-utils")]
|
||||
pub mod test_utils;
|
||||
mod validated_state_diff;
|
||||
|
||||
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.
|
||||
///
|
||||
/// Can only be constructed by the transaction validation functions inside this crate, ensuring the
|
||||
/// diff has been checked before any state mutation occurs.
|
||||
/// It can only be constructed by the transaction validation functions inside this crate, ensuring
|
||||
/// 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);
|
||||
|
||||
#[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 {
|
||||
pub fn from_public_transaction(
|
||||
tx: &PublicTransaction,
|
||||
|
||||
@ -25,3 +25,6 @@ log.workspace = true
|
||||
hex.workspace = true
|
||||
borsh.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;
|
||||
#[cfg(test)]
|
||||
use lee::{Account, PrivateKey, PublicKey, V03State, ValidatedStateDiff};
|
||||
|
||||
use crate::{
|
||||
HashType,
|
||||
@ -13,6 +21,33 @@ pub fn sequencer_sign_key_for_testing() -> lee::PrivateKey {
|
||||
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
|
||||
|
||||
/// Produce dummy block with provided transactions + clock transaction an the end.
|
||||
|
||||
@ -260,9 +260,112 @@ fn validate_doesnt_modify_account(
|
||||
|
||||
#[cfg(test)]
|
||||
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]
|
||||
fn system_account_ids_are_distinct_and_non_default() {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user