use either::Either;
use freenet_stdlib::prelude::*;
use std::sync::Arc;
use crate::contract::executor::mock_wasm_runtime::{MockWasmRuntime, ValidateOverride};
use crate::contract::executor::{ContractExecutor, Executor, UpsertResult};
use crate::wasm_runtime::MockStateStorage;
async fn create_executor() -> Executor<MockWasmRuntime, MockStateStorage> {
let storage = MockStateStorage::new();
Executor::new_mock_wasm("related_test", storage, None, None, None)
.await
.expect("create MockWasmRuntime executor")
}
fn make_contract(code_bytes: &[u8]) -> ContractContainer {
let code = ContractCode::from(code_bytes.to_vec());
let params = Parameters::from(vec![]);
ContractContainer::Wasm(ContractWasmAPIVersion::V1(WrappedContract::new(
Arc::new(code),
params,
)))
}
#[tokio::test(flavor = "current_thread")]
async fn test_put_with_related_succeeds() {
let mut executor = create_executor().await;
let gate_contract = make_contract(b"gate_code");
let gate_key = gate_contract.key();
let gate_state = WrappedState::new(br#"{"open": true}"#.to_vec());
let result = executor
.upsert_contract_state(
gate_key,
Either::Left(gate_state.clone()),
RelatedContracts::default(),
Some(gate_contract),
)
.await;
assert!(result.is_ok(), "gate contract PUT should succeed");
let gated_contract = make_contract(b"gated_code");
let gated_key = gated_contract.key();
let gated_state = WrappedState::new(br#"{"count": 0}"#.to_vec());
executor.runtime.validate_overrides.insert(
*gated_key.id(),
ValidateOverride::RequestRelated(vec![*gate_key.id()]),
);
let result = executor
.upsert_contract_state(
gated_key,
Either::Left(gated_state),
RelatedContracts::default(),
Some(gated_contract),
)
.await;
assert!(
result.is_ok(),
"gated contract PUT should succeed: {result:?}"
);
assert!(
matches!(result.unwrap(), UpsertResult::Updated(_)),
"expected Updated result"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_put_with_gate_closed() {
let mut executor = create_executor().await;
let gate_contract = make_contract(b"gate_code_closed");
let gate_key = gate_contract.key();
let gate_state = WrappedState::new(br#"{"open": false}"#.to_vec());
executor
.upsert_contract_state(
gate_key,
Either::Left(gate_state),
RelatedContracts::default(),
Some(gate_contract),
)
.await
.expect("gate PUT");
let gated_contract = make_contract(b"gated_code_closed");
let gated_key = gated_contract.key();
let gated_state = WrappedState::new(br#"{"count": 0}"#.to_vec());
executor
.runtime
.validate_overrides
.insert(*gated_key.id(), ValidateOverride::Invalid);
let result = executor
.upsert_contract_state(
gated_key,
Either::Left(gated_state),
RelatedContracts::default(),
Some(gated_contract),
)
.await;
assert!(
result.is_err(),
"gated contract PUT should fail when invalid"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_put_self_reference_rejected() {
let mut executor = create_executor().await;
let contract = make_contract(b"self_ref_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
executor
.runtime
.validate_overrides
.insert(*key.id(), ValidateOverride::RequestRelated(vec![*key.id()]));
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(result.is_err(), "self-reference should be rejected");
let err_msg = format!("{:?}", result.unwrap_err());
assert!(
err_msg.contains("cannot request itself"),
"error should mention self-reference: {err_msg}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_put_too_many_related_rejected() {
let mut executor = create_executor().await;
let contract = make_contract(b"too_many_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
let too_many_ids: Vec<ContractInstanceId> = (0..11u8)
.map(|i| {
let contract = make_contract(&[i, 100, 200]);
*contract.key().id()
})
.collect();
executor
.runtime
.validate_overrides
.insert(*key.id(), ValidateOverride::RequestRelated(too_many_ids));
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(result.is_err(), "too many related should be rejected");
let err_msg = format!("{:?}", result.unwrap_err());
assert!(
err_msg.contains("limit is 10"),
"error should mention limit: {err_msg}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_put_missing_related_errors() {
let mut executor = create_executor().await;
let contract = make_contract(b"missing_related_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
let nonexistent_contract = make_contract(b"nonexistent_contract_code_xyz");
let nonexistent_id = *nonexistent_contract.key().id();
executor.runtime.validate_overrides.insert(
*key.id(),
ValidateOverride::RequestRelated(vec![nonexistent_id]),
);
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(
result.is_err(),
"missing related contract should error, not hang"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_depth_exceeded_rejected() {
let mut executor = create_executor().await;
let related_contract = make_contract(b"related_depth_code");
let related_key = related_contract.key();
let related_state = WrappedState::new(vec![10, 20]);
executor
.upsert_contract_state(
related_key,
Either::Left(related_state),
RelatedContracts::default(),
Some(related_contract),
)
.await
.expect("related contract PUT");
let contract = make_contract(b"depth_test_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
executor.runtime.validate_overrides.insert(
*key.id(),
ValidateOverride::AlwaysRequestRelated(vec![*related_key.id()]),
);
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(result.is_err(), "depth exceeded should be rejected");
let err_msg = format!("{:?}", result.unwrap_err());
assert!(
err_msg.contains("depth=1 limit exceeded"),
"error should mention depth limit: {err_msg}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_empty_request_related() {
let mut executor = create_executor().await;
let contract = make_contract(b"empty_related_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
executor
.runtime
.validate_overrides
.insert(*key.id(), ValidateOverride::EmptyRequestRelated);
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(result.is_err(), "empty RequestRelated should be rejected");
let err_msg = format!("{:?}", result.unwrap_err());
assert!(
err_msg.contains("empty list"),
"error should mention empty list: {err_msg}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_duplicate_ids_deduped() {
let mut executor = create_executor().await;
let related_contract = make_contract(b"dedup_related_code");
let related_key = related_contract.key();
let related_state = WrappedState::new(vec![10, 20]);
executor
.upsert_contract_state(
related_key,
Either::Left(related_state),
RelatedContracts::default(),
Some(related_contract),
)
.await
.expect("related contract PUT");
let contract = make_contract(b"dedup_test_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
executor.runtime.validate_overrides.insert(
*key.id(),
ValidateOverride::RequestRelated(vec![*related_key.id(), *related_key.id()]),
);
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(
result.is_ok(),
"duplicate IDs should be deduped and succeed: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_existing_contract_put_with_related() {
let mut executor = create_executor().await;
let gate_contract = make_contract(b"gate_merge_code");
let gate_key = gate_contract.key();
let gate_state = WrappedState::new(br#"{"open": true}"#.to_vec());
executor
.upsert_contract_state(
gate_key,
Either::Left(gate_state),
RelatedContracts::default(),
Some(gate_contract),
)
.await
.expect("gate PUT");
let gated_contract = make_contract(b"gated_merge_code");
let gated_key = gated_contract.key();
let initial_state = WrappedState::new(br#"{"count": 0}"#.to_vec());
executor
.upsert_contract_state(
gated_key,
Either::Left(initial_state),
RelatedContracts::default(),
Some(gated_contract.clone()),
)
.await
.expect("gated initial PUT");
executor.runtime.validate_overrides.insert(
*gated_key.id(),
ValidateOverride::RequestRelated(vec![*gate_key.id()]),
);
let updated_state = WrappedState::new(br#"{"count": 1}"#.to_vec());
let result = executor
.upsert_contract_state(
gated_key,
Either::Left(updated_state),
RelatedContracts::default(),
Some(gated_contract),
)
.await;
assert!(
result.is_ok(),
"existing contract PUT with related should succeed: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_with_related_succeeds() {
let mut executor = create_executor().await;
let gate_contract = make_contract(b"gate_update_code");
let gate_key = gate_contract.key();
let gate_state = WrappedState::new(br#"{"open": true}"#.to_vec());
executor
.upsert_contract_state(
gate_key,
Either::Left(gate_state),
RelatedContracts::default(),
Some(gate_contract),
)
.await
.expect("gate PUT");
let gated_contract = make_contract(b"gated_update_code");
let gated_key = gated_contract.key();
let initial_state = WrappedState::new(br#"{"count": 0}"#.to_vec());
executor
.upsert_contract_state(
gated_key,
Either::Left(initial_state),
RelatedContracts::default(),
Some(gated_contract),
)
.await
.expect("gated initial PUT");
executor.runtime.validate_overrides.insert(
*gated_key.id(),
ValidateOverride::RequestRelated(vec![*gate_key.id()]),
);
let delta = StateDelta::from(br#"{"count": 1}"#.to_vec());
let result = executor
.upsert_contract_state(
gated_key,
Either::Right(delta),
RelatedContracts::default(),
None,
)
.await;
assert!(
result.is_ok(),
"update with related validation should succeed: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_exactly_at_limit_succeeds() {
let mut executor = create_executor().await;
let mut related_ids = Vec::new();
for i in 0..10u8 {
let contract = make_contract(&[i, 200, 200]);
let key = contract.key();
related_ids.push(*key.id());
executor
.upsert_contract_state(
key,
Either::Left(WrappedState::new(vec![i])),
RelatedContracts::default(),
Some(contract),
)
.await
.expect("related contract PUT");
}
let contract = make_contract(b"boundary_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
executor
.runtime
.validate_overrides
.insert(*key.id(), ValidateOverride::RequestRelated(related_ids));
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(
result.is_ok(),
"exactly 10 related contracts should succeed: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_multiple_related_contracts() {
let mut executor = create_executor().await;
let mut related_ids = Vec::new();
for i in 0..3u8 {
let contract = make_contract(&[i, 150, 150]);
let key = contract.key();
related_ids.push(*key.id());
executor
.upsert_contract_state(
key,
Either::Left(WrappedState::new(vec![10 + i])),
RelatedContracts::default(),
Some(contract),
)
.await
.expect("related contract PUT");
}
let contract = make_contract(b"multi_related_code");
let key = contract.key();
let state = WrappedState::new(vec![1, 2, 3]);
executor
.runtime
.validate_overrides
.insert(*key.id(), ValidateOverride::RequestRelated(related_ids));
let result = executor
.upsert_contract_state(
key,
Either::Left(state),
RelatedContracts::default(),
Some(contract),
)
.await;
assert!(
result.is_ok(),
"multiple related contracts should succeed: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_missing_related_local_only_errors() {
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_missing_related_target");
let target_key = target_contract.key();
let initial_state = WrappedState::new(br#"{"v":0}"#.to_vec());
executor
.upsert_contract_state(
target_key,
Either::Left(initial_state),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
let nonexistent_contract = make_contract(b"update_missing_related_target_missing");
let nonexistent_id = *nonexistent_contract.key().id();
executor.runtime.validate_overrides.insert(
*target_key.id(),
ValidateOverride::RequestRelated(vec![nonexistent_id]),
);
let delta = StateDelta::from(br#"{"v":1}"#.to_vec());
let result = executor
.upsert_contract_state(
target_key,
Either::Right(delta),
RelatedContracts::default(),
None,
)
.await;
assert!(
result.is_err(),
"UPDATE that triggers RequestRelated for a missing contract \
must error in local-only mode (mock executor has no op_manager); \
got {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_missing_related_escalates_to_network_when_stubbed() {
use std::cell::RefCell;
use std::rc::Rc;
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_network_escalate_target");
let target_key = target_contract.key();
let initial_state = WrappedState::new(br#"{"v":0}"#.to_vec());
executor
.upsert_contract_state(
target_key,
Either::Left(initial_state),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
let related_contract = make_contract(b"update_network_escalate_related");
let related_id = *related_contract.key().id();
executor.runtime.validate_overrides.insert(
*target_key.id(),
ValidateOverride::RequestRelated(vec![related_id]),
);
let calls: Rc<RefCell<Vec<ContractInstanceId>>> = Rc::new(RefCell::new(Vec::new()));
let calls_inner = calls.clone();
crate::contract::executor::runtime::set_test_network_fetch_override(Some(Rc::new(move |id| {
calls_inner.borrow_mut().push(id);
Ok(freenet_stdlib::prelude::WrappedState::new(
br#"{"network_fetched":true}"#.to_vec(),
))
})));
let delta = StateDelta::from(br#"{"v":1}"#.to_vec());
let result = executor
.upsert_contract_state(
target_key,
Either::Right(delta),
RelatedContracts::default(),
None,
)
.await;
crate::contract::executor::runtime::set_test_network_fetch_override(None);
assert!(
result.is_ok(),
"UPDATE must succeed once the network stub services the missing related contract: {result:?}"
);
let observed_calls = calls.borrow().clone();
assert!(
observed_calls.contains(&related_id),
"fetch_related_via_network must have been invoked for related_id={related_id}; \
observed calls: {observed_calls:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_missing_related_network_fetch_fails() {
use std::cell::RefCell;
use std::rc::Rc;
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_network_fail_target");
let target_key = target_contract.key();
let initial_state = WrappedState::new(br#"{"v":0}"#.to_vec());
executor
.upsert_contract_state(
target_key,
Either::Left(initial_state),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
let related_contract = make_contract(b"update_network_fail_related");
let related_id = *related_contract.key().id();
executor.runtime.validate_overrides.insert(
*target_key.id(),
ValidateOverride::RequestRelated(vec![related_id]),
);
let calls: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
let calls_inner = calls.clone();
crate::contract::executor::runtime::set_test_network_fetch_override(Some(Rc::new(move |id| {
*calls_inner.borrow_mut() += 1;
Err(crate::contract::ExecutorError::request(
freenet_stdlib::client_api::ContractError::MissingRelated { key: id },
))
})));
let delta = StateDelta::from(br#"{"v":1}"#.to_vec());
let result = executor
.upsert_contract_state(
target_key,
Either::Right(delta),
RelatedContracts::default(),
None,
)
.await;
crate::contract::executor::runtime::set_test_network_fetch_override(None);
assert_eq!(
*calls.borrow(),
1,
"stub must be called exactly once (one related id requested)"
);
assert!(
result.is_err(),
"UPDATE must surface the network fetch failure as an error: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_state_requires_related_fetches_and_retries() {
use std::cell::RefCell;
use std::rc::Rc;
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_requires_target");
let target_key = target_contract.key();
let initial_state = WrappedState::new(br#"{"v":0}"#.to_vec());
executor
.upsert_contract_state(
target_key,
Either::Left(initial_state),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
let related_contract = make_contract(b"update_requires_related");
let related_id = *related_contract.key().id();
executor.runtime.update_overrides.insert(
*target_key.id(),
crate::contract::executor::mock_wasm_runtime::UpdateOverride::RequiresRelated(vec![
related_id,
]),
);
let calls: Rc<RefCell<Vec<ContractInstanceId>>> = Rc::new(RefCell::new(Vec::new()));
let calls_inner = calls.clone();
crate::contract::executor::runtime::set_test_network_fetch_override(Some(Rc::new(move |id| {
calls_inner.borrow_mut().push(id);
Ok(freenet_stdlib::prelude::WrappedState::new(
br#"{"network_supplied_related":true}"#.to_vec(),
))
})));
let delta = StateDelta::from(br#"{"v":1}"#.to_vec());
let result = executor
.upsert_contract_state(
target_key,
Either::Right(delta),
RelatedContracts::default(),
None,
)
.await;
crate::contract::executor::runtime::set_test_network_fetch_override(None);
assert!(
result.is_ok(),
"UPDATE must succeed once network supplies the related state: {result:?}"
);
let observed_calls = calls.borrow().clone();
assert!(
observed_calls.contains(&related_id),
"fetch_related_via_network must have been invoked for the requires(missing) path; \
observed calls: {observed_calls:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_state_requires_related_network_fail_returns_missing() {
use std::cell::RefCell;
use std::rc::Rc;
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_requires_fail_target");
let target_key = target_contract.key();
executor
.upsert_contract_state(
target_key,
Either::Left(WrappedState::new(br#"{"v":0}"#.to_vec())),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
let related_contract = make_contract(b"update_requires_fail_related");
let related_id = *related_contract.key().id();
executor.runtime.update_overrides.insert(
*target_key.id(),
crate::contract::executor::mock_wasm_runtime::UpdateOverride::RequiresRelated(vec![
related_id,
]),
);
let calls: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
let calls_inner = calls.clone();
crate::contract::executor::runtime::set_test_network_fetch_override(Some(Rc::new(move |id| {
*calls_inner.borrow_mut() += 1;
Err(crate::contract::ExecutorError::request(
freenet_stdlib::client_api::ContractError::MissingRelated { key: id },
))
})));
let result = executor
.upsert_contract_state(
target_key,
Either::Right(StateDelta::from(br#"{"v":1}"#.to_vec())),
RelatedContracts::default(),
None,
)
.await;
crate::contract::executor::runtime::set_test_network_fetch_override(None);
assert_eq!(*calls.borrow(), 1, "stub must be called once");
assert!(
result.is_err(),
"UPDATE must surface network fetch failure: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_state_requires_related_depth_exceeded_rejected() {
use std::rc::Rc;
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_requires_depth_target");
let target_key = target_contract.key();
executor
.upsert_contract_state(
target_key,
Either::Left(WrappedState::new(br#"{"v":0}"#.to_vec())),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
let related_contract = make_contract(b"update_requires_depth_related");
let related_id = *related_contract.key().id();
executor.runtime.update_overrides.insert(
*target_key.id(),
crate::contract::executor::mock_wasm_runtime::UpdateOverride::AlwaysRequiresRelated(vec![
related_id,
]),
);
crate::contract::executor::runtime::set_test_network_fetch_override(Some(Rc::new(|_id| {
Ok(freenet_stdlib::prelude::WrappedState::new(
br#"{"network_supplied_related":true}"#.to_vec(),
))
})));
let result = executor
.upsert_contract_state(
target_key,
Either::Right(StateDelta::from(br#"{"v":1}"#.to_vec())),
RelatedContracts::default(),
None,
)
.await;
crate::contract::executor::runtime::set_test_network_fetch_override(None);
assert!(
result.is_err(),
"depth>1 UPDATE requires() must be rejected: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_state_requires_related_too_many_rejected() {
use std::cell::RefCell;
use std::rc::Rc;
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_requires_toomany_target");
let target_key = target_contract.key();
executor
.upsert_contract_state(
target_key,
Either::Left(WrappedState::new(br#"{"v":0}"#.to_vec())),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
let too_many: Vec<ContractInstanceId> = (0..11)
.map(|i| *make_contract(&[0xFF, i, 0xAA]).key().id())
.collect();
executor.runtime.update_overrides.insert(
*target_key.id(),
crate::contract::executor::mock_wasm_runtime::UpdateOverride::AlwaysRequiresRelated(
too_many,
),
);
let calls: Rc<RefCell<usize>> = Rc::new(RefCell::new(0));
let calls_inner = calls.clone();
crate::contract::executor::runtime::set_test_network_fetch_override(Some(Rc::new(
move |_id| {
*calls_inner.borrow_mut() += 1;
Ok(freenet_stdlib::prelude::WrappedState::new(b"x".to_vec()))
},
)));
let result = executor
.upsert_contract_state(
target_key,
Either::Right(StateDelta::from(br#"{"v":1}"#.to_vec())),
RelatedContracts::default(),
None,
)
.await;
crate::contract::executor::runtime::set_test_network_fetch_override(None);
assert_eq!(
*calls.borrow(),
0,
"guard must reject before any fetch fires; stub was called {} times",
*calls.borrow()
);
assert!(
result.is_err(),
"UPDATE requires() with >MAX must be rejected: {result:?}"
);
}
#[tokio::test(flavor = "current_thread")]
async fn test_update_state_requires_related_self_reference_rejected() {
let mut executor = create_executor().await;
let target_contract = make_contract(b"update_requires_self_target");
let target_key = target_contract.key();
executor
.upsert_contract_state(
target_key,
Either::Left(WrappedState::new(br#"{"v":0}"#.to_vec())),
RelatedContracts::default(),
Some(target_contract),
)
.await
.expect("initial PUT");
executor.runtime.update_overrides.insert(
*target_key.id(),
crate::contract::executor::mock_wasm_runtime::UpdateOverride::AlwaysRequiresRelated(vec![
*target_key.id(),
]),
);
let result = executor
.upsert_contract_state(
target_key,
Either::Right(StateDelta::from(br#"{"v":1}"#.to_vec())),
RelatedContracts::default(),
None,
)
.await;
assert!(
result.is_err(),
"self-reference in update_state requires() must be rejected: {result:?}"
);
}