tests(server): property-based testing for ordering independency for hash
This commit is contained in:
5
server/Cargo.lock
generated
5
server/Cargo.lock
generated
@@ -745,6 +745,7 @@ dependencies = [
|
||||
"memsafe",
|
||||
"mutants",
|
||||
"pem",
|
||||
"proptest",
|
||||
"prost",
|
||||
"prost-types",
|
||||
"rand 0.10.0",
|
||||
@@ -3647,9 +3648,9 @@ dependencies = [
|
||||
|
||||
[[package]]
|
||||
name = "proptest"
|
||||
version = "1.10.0"
|
||||
version = "1.11.0"
|
||||
source = "registry+https://github.com/rust-lang/crates.io-index"
|
||||
checksum = "37566cb3fdacef14c0737f9546df7cfeadbfbc9fef10991038bf5015d0c80532"
|
||||
checksum = "4b45fcc2344c680f5025fe57779faef368840d0bd1f42f216291f0dc4ace4744"
|
||||
dependencies = [
|
||||
"bit-set",
|
||||
"bit-vec",
|
||||
|
||||
@@ -63,5 +63,6 @@ mutants.workspace = true
|
||||
|
||||
[dev-dependencies]
|
||||
insta = "1.46.3"
|
||||
proptest = "1.11.0"
|
||||
rstest.workspace = true
|
||||
test-log = { version = "0.2", default-features = false, features = ["trace"] }
|
||||
|
||||
@@ -1,4 +1,6 @@
|
||||
use crate::{actors::keyholder, crypto::integrity::hashing::Hashable, safe_cell::SafeCellHandle as _};
|
||||
use crate::{
|
||||
actors::keyholder, crypto::integrity::hashing::Hashable, safe_cell::SafeCellHandle as _,
|
||||
};
|
||||
use hmac::{Hmac, Mac as _};
|
||||
use sha2::Sha256;
|
||||
|
||||
@@ -43,7 +45,6 @@ pub enum Error {
|
||||
|
||||
#[error("Integrity MAC mismatch for entity {entity_kind}")]
|
||||
MacMismatch { entity_kind: &'static str },
|
||||
|
||||
}
|
||||
|
||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||
@@ -203,13 +204,19 @@ mod tests {
|
||||
use diesel::{ExpressionMethods as _, QueryDsl};
|
||||
use diesel_async::RunQueryDsl;
|
||||
use kameo::{actor::ActorRef, prelude::Spawn};
|
||||
use rand::seq::SliceRandom;
|
||||
use sha2::Digest;
|
||||
|
||||
use proptest::prelude::*;
|
||||
|
||||
use crate::{
|
||||
actors::keyholder::{Bootstrap, KeyHolder}, crypto::integrity::hashing::Hashable, db::{self, schema}, safe_cell::{SafeCell, SafeCellHandle as _}
|
||||
actors::keyholder::{Bootstrap, KeyHolder},
|
||||
db::{self, schema},
|
||||
safe_cell::{SafeCell, SafeCellHandle as _},
|
||||
};
|
||||
|
||||
use super::{Error, Integrable, sign_entity, verify_entity};
|
||||
use super::{hashing::Hashable, payload_hash};
|
||||
|
||||
#[derive(Clone)]
|
||||
struct DummyEntity {
|
||||
|
||||
@@ -104,4 +104,4 @@ impl Hashable for chrono::DateTime<chrono::Utc> {
|
||||
fn hash<H: Digest>(&self, hasher: &mut H) {
|
||||
hasher.update(&self.timestamp_millis().to_be_bytes());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -84,8 +84,6 @@ fn shared() -> SharedGrantSettings {
|
||||
}
|
||||
}
|
||||
|
||||
// ── analyze ─────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn analyze_matches_empty_calldata() {
|
||||
let m = EtherTransfer::analyze(&ctx(ALLOWED, U256::from(1_000u64))).unwrap();
|
||||
@@ -102,8 +100,6 @@ fn analyze_rejects_nonempty_calldata() {
|
||||
assert!(EtherTransfer::analyze(&context).is_none());
|
||||
}
|
||||
|
||||
// ── evaluate ────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn evaluate_passes_for_allowed_target() {
|
||||
let db = db::create_test_pool().await;
|
||||
@@ -276,8 +272,6 @@ async fn evaluate_passes_at_exactly_volume_limit() {
|
||||
);
|
||||
}
|
||||
|
||||
// ── try_find_grant ───────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn try_find_grant_roundtrip() {
|
||||
let db = db::create_test_pool().await;
|
||||
@@ -336,7 +330,36 @@ async fn try_find_grant_wrong_target_returns_none() {
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
// ── find_all_grants ──────────────────────────────────────────────────────
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn target_order_does_not_affect_hash(
|
||||
raw_addrs in proptest::collection::vec(proptest::prelude::any::<[u8; 20]>(), 0..8),
|
||||
seed in proptest::prelude::any::<u64>(),
|
||||
max_volume in proptest::prelude::any::<u64>(),
|
||||
window_secs in 1i64..=86400,
|
||||
) {
|
||||
use rand::{SeedableRng, seq::SliceRandom};
|
||||
use sha2::Digest;
|
||||
use crate::crypto::integrity::hashing::Hashable;
|
||||
|
||||
let addrs: Vec<Address> = raw_addrs.iter().map(|b| Address::from(*b)).collect();
|
||||
let mut shuffled = addrs.clone();
|
||||
shuffled.shuffle(&mut rand::rngs::StdRng::seed_from_u64(seed));
|
||||
|
||||
let limit = VolumeRateLimit {
|
||||
max_volume: U256::from(max_volume),
|
||||
window: Duration::seconds(window_secs),
|
||||
};
|
||||
|
||||
let mut h1 = sha2::Sha256::new();
|
||||
Settings { target: addrs, limit: limit.clone() }.hash(&mut h1);
|
||||
|
||||
let mut h2 = sha2::Sha256::new();
|
||||
Settings { target: shuffled, limit }.hash(&mut h2);
|
||||
|
||||
proptest::prop_assert_eq!(h1.finalize(), h2.finalize());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn find_all_grants_empty_db() {
|
||||
|
||||
@@ -1,15 +1,5 @@
|
||||
use std::collections::HashMap;
|
||||
|
||||
use alloy::{
|
||||
primitives::{Address, U256},
|
||||
sol_types::SolCall,
|
||||
};
|
||||
use arbiter_tokens_registry::evm::nonfungible::{self, TokenInfo};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use diesel::dsl::{auto_type, insert_into};
|
||||
use diesel::sqlite::Sqlite;
|
||||
use diesel::{ExpressionMethods, prelude::*};
|
||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||
use crate::db::schema::{
|
||||
evm_basic_grant, evm_token_transfer_grant, evm_token_transfer_log,
|
||||
evm_token_transfer_volume_limit,
|
||||
@@ -30,6 +20,16 @@ use crate::{
|
||||
},
|
||||
evm::policies::CombinedSettings,
|
||||
};
|
||||
use alloy::{
|
||||
primitives::{Address, U256},
|
||||
sol_types::SolCall,
|
||||
};
|
||||
use arbiter_tokens_registry::evm::nonfungible::{self, TokenInfo};
|
||||
use chrono::{DateTime, Duration, Utc};
|
||||
use diesel::dsl::{auto_type, insert_into};
|
||||
use diesel::sqlite::Sqlite;
|
||||
use diesel::{ExpressionMethods, prelude::*};
|
||||
use diesel_async::{AsyncConnection, RunQueryDsl};
|
||||
|
||||
use super::{DatabaseID, EvalContext, EvalViolation};
|
||||
|
||||
|
||||
@@ -101,8 +101,6 @@ fn shared() -> SharedGrantSettings {
|
||||
}
|
||||
}
|
||||
|
||||
// ── analyze ─────────────────────────────────────────────────────────────
|
||||
|
||||
#[test]
|
||||
fn analyze_known_token_valid_calldata() {
|
||||
let calldata = transfer_calldata(RECIPIENT, U256::from(100u64));
|
||||
@@ -128,8 +126,6 @@ fn analyze_empty_calldata_returns_none() {
|
||||
assert!(TokenTransfer::analyze(&ctx(DAI, Bytes::new())).is_none());
|
||||
}
|
||||
|
||||
// ── evaluate ────────────────────────────────────────────────────────────
|
||||
|
||||
#[tokio::test]
|
||||
async fn evaluate_rejects_nonzero_eth_value() {
|
||||
let db = db::create_test_pool().await;
|
||||
@@ -412,7 +408,39 @@ async fn try_find_grant_unknown_token_returns_none() {
|
||||
assert!(found.is_none());
|
||||
}
|
||||
|
||||
// ── find_all_grants ──────────────────────────────────────────────────────
|
||||
proptest::proptest! {
|
||||
#[test]
|
||||
fn volume_limits_order_does_not_affect_hash(
|
||||
raw_limits in proptest::collection::vec(
|
||||
(proptest::prelude::any::<u64>(), 1i64..=86400),
|
||||
0..8,
|
||||
),
|
||||
seed in proptest::prelude::any::<u64>(),
|
||||
) {
|
||||
use rand::{SeedableRng, seq::SliceRandom};
|
||||
use sha2::Digest;
|
||||
use crate::crypto::integrity::hashing::Hashable;
|
||||
|
||||
let limits: Vec<VolumeRateLimit> = raw_limits
|
||||
.iter()
|
||||
.map(|(max_vol, window_secs)| VolumeRateLimit {
|
||||
max_volume: U256::from(*max_vol),
|
||||
window: Duration::seconds(*window_secs),
|
||||
})
|
||||
.collect();
|
||||
|
||||
let mut shuffled = limits.clone();
|
||||
shuffled.shuffle(&mut rand::rngs::StdRng::seed_from_u64(seed));
|
||||
|
||||
let mut h1 = sha2::Sha256::new();
|
||||
Settings { token_contract: DAI, target: None, volume_limits: limits }.hash(&mut h1);
|
||||
|
||||
let mut h2 = sha2::Sha256::new();
|
||||
Settings { token_contract: DAI, target: None, volume_limits: shuffled }.hash(&mut h2);
|
||||
|
||||
proptest::prop_assert_eq!(h1.finalize(), h2.finalize());
|
||||
}
|
||||
}
|
||||
|
||||
#[tokio::test]
|
||||
async fn find_all_grants_empty_db() {
|
||||
|
||||
Reference in New Issue
Block a user