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)
.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:?}"
);
}