use freenet_stdlib::prelude::*;
use crate::contract::executor::mock_wasm_runtime::MockWasmRuntime;
use crate::wasm_runtime::{
ContractRuntimeInterface, ContractStore, DelegateStore, InMemoryContractStore, Runtime,
SecretsStore,
};
const MOCK_ALIGNED_CONTRACT: &str = "test-contract-mock-aligned";
struct WasmTestSetup {
runtime: Runtime,
contract_key: ContractKey,
_temp_dir: tempfile::TempDir,
}
async fn setup_wasm_runtime() -> Result<WasmTestSetup, Box<dyn std::error::Error>> {
let contract = tokio::task::spawn_blocking(|| {
crate::test_utils::load_contract(MOCK_ALIGNED_CONTRACT, Parameters::from(vec![]))
})
.await??;
let contract_key = contract.key();
let temp_dir = crate::util::tests::get_temp_dir();
let db = crate::contract::storages::Storage::new(temp_dir.path()).await?;
let mut contract_store =
ContractStore::new(temp_dir.path().join("contract"), 10_000, db.clone())?;
let delegate_store = DelegateStore::new(temp_dir.path().join("delegate"), 10_000, db.clone())?;
let secrets_store = SecretsStore::new(temp_dir.path().join("secrets"), Default::default(), db)?;
contract_store.store_contract(contract)?;
let runtime = Runtime::build(contract_store, delegate_store, secrets_store, false)?;
Ok(WasmTestSetup {
runtime,
contract_key,
_temp_dir: temp_dir,
})
}
fn setup_mock() -> MockWasmRuntime {
MockWasmRuntime {
contract_store: InMemoryContractStore::new(),
validate_overrides: std::collections::HashMap::new(),
}
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_validate_state() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let large_state: Vec<u8> = (0..100_000).map(|i| (i % 256) as u8).collect();
let test_states = [
WrappedState::new(vec![]),
WrappedState::new(vec![1, 2, 3]),
WrappedState::new(vec![0; 1024]),
WrappedState::new(b"hello world".to_vec()),
WrappedState::new(large_state),
];
let params = Parameters::from(vec![]);
let related = RelatedContracts::default();
for (i, state) in test_states.iter().enumerate() {
let wasm_result =
wasm.runtime
.validate_state(&wasm.contract_key, ¶ms, state, &related)?;
let mock_result = mock.validate_state(&wasm.contract_key, ¶ms, state, &related)?;
assert_eq!(
wasm_result, mock_result,
"validate_state divergence on case {i}: wasm={wasm_result:?}, mock={mock_result:?}"
);
assert_eq!(wasm_result, ValidateResult::Valid);
}
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_update_state_full() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let current = WrappedState::new(vec![10, 20, 30]);
let incoming = WrappedState::new(vec![40, 50, 60]);
let params = Parameters::from(vec![]);
let updates = vec![UpdateData::State(incoming.clone().into())];
let wasm_result = wasm
.runtime
.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let mock_result = mock.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
assert_eq!(
wasm_result.unwrap_valid().as_ref(),
mock_result.unwrap_valid().as_ref(),
"update_state(State) must produce identical output"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_update_state_delta() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let current = WrappedState::new(vec![1, 2, 3]);
let delta = StateDelta::from(vec![7, 8, 9]);
let params = Parameters::from(vec![]);
let updates = vec![UpdateData::Delta(delta.clone())];
let wasm_result = wasm
.runtime
.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let mock_result = mock.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let wasm_state = wasm_result.unwrap_valid();
let mock_state = mock_result.unwrap_valid();
assert_eq!(
wasm_state.as_ref(),
mock_state.as_ref(),
"update_state(Delta) must produce identical output"
);
assert_eq!(wasm_state.as_ref(), delta.as_ref());
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_update_state_last_wins() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let current = WrappedState::new(vec![0]);
let params = Parameters::from(vec![]);
let updates = vec![
UpdateData::State(WrappedState::new(vec![1, 1]).into()),
UpdateData::Delta(StateDelta::from(vec![2, 2])),
UpdateData::State(WrappedState::new(vec![3, 3, 3]).into()),
];
let wasm_result = wasm
.runtime
.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let mock_result = mock.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let wasm_state = wasm_result.unwrap_valid();
let mock_state = mock_result.unwrap_valid();
assert_eq!(wasm_state.as_ref(), mock_state.as_ref());
assert_eq!(wasm_state.as_ref(), &[3, 3, 3], "Last State should win");
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_update_state_empty() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let current = WrappedState::new(vec![42, 43]);
let params = Parameters::from(vec![]);
let updates: Vec<UpdateData<'_>> = vec![];
let wasm_result = wasm
.runtime
.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let mock_result = mock.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
assert_eq!(
wasm_result.unwrap_valid().as_ref(),
mock_result.unwrap_valid().as_ref(),
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_summarize_state() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let test_states = [
WrappedState::new(vec![1, 2, 3, 4, 5]),
WrappedState::new(vec![0; 100]),
WrappedState::new(b"conformance test data".to_vec()),
];
let params = Parameters::from(vec![]);
for (i, state) in test_states.iter().enumerate() {
let wasm_summary = wasm
.runtime
.summarize_state(&wasm.contract_key, ¶ms, state)?;
let mock_summary = mock.summarize_state(&wasm.contract_key, ¶ms, state)?;
assert_eq!(
wasm_summary.as_ref(),
mock_summary.as_ref(),
"summarize_state divergence on case {i}"
);
let expected = blake3::hash(state.as_ref());
assert_eq!(wasm_summary.as_ref(), expected.as_bytes());
}
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_get_state_delta() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let state = WrappedState::new(vec![10, 20, 30, 40, 50]);
let summary = StateSummary::from(vec![0; 32]);
let params = Parameters::from(vec![]);
let wasm_delta = wasm
.runtime
.get_state_delta(&wasm.contract_key, ¶ms, &state, &summary)?;
let mock_delta = mock.get_state_delta(&wasm.contract_key, ¶ms, &state, &summary)?;
assert_eq!(
wasm_delta.as_ref(),
mock_delta.as_ref(),
"get_state_delta must produce identical output"
);
assert_eq!(wasm_delta.as_ref(), state.as_ref());
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_round_trip() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let state = WrappedState::new(vec![100, 200, 150]);
let params = Parameters::from(vec![]);
let wasm_summary = wasm
.runtime
.summarize_state(&wasm.contract_key, ¶ms, &state)?;
let mock_summary = mock.summarize_state(&wasm.contract_key, ¶ms, &state)?;
assert_eq!(wasm_summary.as_ref(), mock_summary.as_ref());
let wasm_delta =
wasm.runtime
.get_state_delta(&wasm.contract_key, ¶ms, &state, &wasm_summary)?;
let mock_delta = mock.get_state_delta(&wasm.contract_key, ¶ms, &state, &mock_summary)?;
assert_eq!(wasm_delta.as_ref(), mock_delta.as_ref());
let old = WrappedState::new(vec![0]);
let wasm_updated = wasm.runtime.update_state(
&wasm.contract_key,
¶ms,
&old,
&[UpdateData::Delta(wasm_delta)],
)?;
let mock_updated = mock.update_state(
&wasm.contract_key,
¶ms,
&old,
&[UpdateData::Delta(mock_delta)],
)?;
let wasm_state = wasm_updated.unwrap_valid();
let mock_state = mock_updated.unwrap_valid();
assert_eq!(
wasm_state.as_ref(),
mock_state.as_ref(),
"Round-trip must produce same state"
);
assert_eq!(
wasm_state.as_ref(),
state.as_ref(),
"Round-trip delta application must recover the original state"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_state_and_delta() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let current = WrappedState::new(vec![0]);
let params = Parameters::from(vec![]);
let state_part = WrappedState::new(vec![11, 22, 33]);
let delta_part = StateDelta::from(vec![44, 55]);
let updates = vec![UpdateData::StateAndDelta {
state: state_part.clone().into(),
delta: delta_part,
}];
let wasm_result = wasm
.runtime
.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let mock_result = mock.update_state(&wasm.contract_key, ¶ms, ¤t, &updates)?;
let wasm_state = wasm_result.unwrap_valid();
let mock_state = mock_result.unwrap_valid();
assert_eq!(wasm_state.as_ref(), mock_state.as_ref());
assert_eq!(
wasm_state.as_ref(),
state_part.as_ref(),
"StateAndDelta must use the State part"
);
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_empty_state() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let empty = WrappedState::new(vec![]);
let params = Parameters::from(vec![]);
let w =
wasm.runtime
.validate_state(&wasm.contract_key, ¶ms, &empty, &Default::default())?;
let m = mock.validate_state(&wasm.contract_key, ¶ms, &empty, &Default::default())?;
assert_eq!(w, m);
let ws = wasm
.runtime
.summarize_state(&wasm.contract_key, ¶ms, &empty)?;
let ms = mock.summarize_state(&wasm.contract_key, ¶ms, &empty)?;
assert_eq!(ws.as_ref(), ms.as_ref());
let summary = StateSummary::from(vec![]);
let wd = wasm
.runtime
.get_state_delta(&wasm.contract_key, ¶ms, &empty, &summary)?;
let md = mock.get_state_delta(&wasm.contract_key, ¶ms, &empty, &summary)?;
assert_eq!(wd.as_ref(), md.as_ref());
assert!(wd.as_ref().is_empty());
Ok(())
}
#[tokio::test(flavor = "multi_thread")]
async fn wasm_conformance_large_state_round_trip() -> Result<(), Box<dyn std::error::Error>> {
let mut wasm = setup_wasm_runtime().await?;
let mut mock = setup_mock();
let large: Vec<u8> = (0u32..100_000).map(|i| (i % 251) as u8).collect();
let state = WrappedState::new(large);
let params = Parameters::from(vec![]);
let wasm_valid =
wasm.runtime
.validate_state(&wasm.contract_key, ¶ms, &state, &Default::default())?;
let mock_valid =
mock.validate_state(&wasm.contract_key, ¶ms, &state, &Default::default())?;
assert_eq!(wasm_valid, mock_valid, "validate diverged on large state");
let wasm_summary = wasm
.runtime
.summarize_state(&wasm.contract_key, ¶ms, &state)?;
let mock_summary = mock.summarize_state(&wasm.contract_key, ¶ms, &state)?;
assert_eq!(
wasm_summary.as_ref(),
mock_summary.as_ref(),
"summarize diverged on large state"
);
let wasm_delta =
wasm.runtime
.get_state_delta(&wasm.contract_key, ¶ms, &state, &wasm_summary)?;
let mock_delta = mock.get_state_delta(&wasm.contract_key, ¶ms, &state, &mock_summary)?;
assert_eq!(
wasm_delta.as_ref(),
mock_delta.as_ref(),
"get_state_delta diverged on large state"
);
let old = WrappedState::new(vec![0]);
let wasm_updated = wasm.runtime.update_state(
&wasm.contract_key,
¶ms,
&old,
&[UpdateData::Delta(wasm_delta)],
)?;
let mock_updated = mock.update_state(
&wasm.contract_key,
¶ms,
&old,
&[UpdateData::Delta(mock_delta)],
)?;
assert_eq!(
wasm_updated.unwrap_valid().as_ref(),
mock_updated.unwrap_valid().as_ref(),
"update diverged on large state"
);
Ok(())
}