use neo_devpack::{prelude::*, NeoVMSyscall};
use std::sync::{Mutex, MutexGuard, OnceLock};
fn runtime_test_lock() -> MutexGuard<'static, ()> {
static TEST_LOCK: OnceLock<Mutex<()>> = OnceLock::new();
match TEST_LOCK.get_or_init(|| Mutex::new(())).lock() {
Ok(guard) => guard,
Err(poisoned) => poisoned.into_inner(),
}
}
fn setup_runtime_test() -> MutexGuard<'static, ()> {
let guard = runtime_test_lock();
NeoVMSyscall::reset_host_state().expect("host syscall state should reset");
guard
}
#[test]
fn runtime_core_syscalls_return_expected_types() {
let _guard = setup_runtime_test();
let timestamp = NeoRuntime::get_time().unwrap();
assert!(timestamp.as_i32_saturating() >= 0);
let timestamp_i64 = NeoRuntime::get_time_i64().unwrap();
assert!(timestamp_i64 >= 0);
let network = NeoRuntime::get_network().unwrap();
assert!(network.as_i32_saturating() >= 0);
let addr_version = NeoRuntime::get_address_version().unwrap();
assert!(addr_version.as_i32_saturating() >= 0);
let gas_left = NeoRuntime::get_gas_left().unwrap();
assert!(gas_left.as_i32_saturating() >= 0);
let trigger = NeoRuntime::get_trigger().unwrap();
assert!(trigger.as_i32_saturating() >= 0);
}
#[test]
fn runtime_script_hash_helpers_produce_bytes() {
let _guard = setup_runtime_test();
let calling = NeoRuntime::get_calling_script_hash().unwrap();
let entry = NeoRuntime::get_entry_script_hash().unwrap();
let executing = NeoRuntime::get_executing_script_hash().unwrap();
assert_eq!(calling.len(), 20);
assert_eq!(entry.len(), 20);
assert_eq!(executing.len(), 20);
}
#[test]
fn runtime_script_hash_i64_helpers_use_hash160_prefix() {
let _guard = setup_runtime_test();
let mut calling = [0u8; 20];
calling[..8].copy_from_slice(&123_i64.to_le_bytes());
let mut entry = [0u8; 20];
entry[..8].copy_from_slice(&(-45_i64).to_le_bytes());
let mut executing = [0u8; 20];
executing[..8].copy_from_slice(&987_i64.to_le_bytes());
NeoVMSyscall::set_active_script_hashes(
&NeoByteString::from_slice(&calling),
&NeoByteString::from_slice(&entry),
&NeoByteString::from_slice(&executing),
)
.unwrap();
assert_eq!(NeoRuntime::get_calling_script_hash_i64().unwrap(), 123);
assert_eq!(NeoRuntime::get_entry_script_hash_i64().unwrap(), -45);
assert_eq!(NeoRuntime::get_executing_script_hash_i64().unwrap(), 987);
}
#[test]
fn runtime_notifications_and_container_are_arrays() {
let _guard = setup_runtime_test();
let notifications = NeoRuntime::get_notifications(None).unwrap();
assert!(notifications.is_empty());
let container = NeoRuntime::get_script_container().unwrap();
assert!(container.is_empty());
}
#[test]
fn storage_context_round_trip() {
let _guard = setup_runtime_test();
let ctx = NeoStorage::get_context().unwrap();
assert!(!ctx.is_read_only());
let read_only = NeoStorage::get_read_only_context().unwrap();
assert!(read_only.is_read_only());
let converted = NeoStorage::as_read_only(&ctx).unwrap();
assert!(converted.is_read_only());
}
#[test]
fn storage_operations_succeed_for_writable_context() {
let _guard = setup_runtime_test();
let ctx = NeoStorage::get_context().unwrap();
let key = NeoByteString::from_slice(b"demo");
let value = NeoByteString::from_slice(b"value");
let stored = NeoStorage::get(&ctx, &key).unwrap();
assert_eq!(stored.len(), 0);
assert!(NeoStorage::put(&ctx, &key, &value).is_ok());
assert!(NeoStorage::delete(&ctx, &key).is_ok());
let iter = NeoStorage::find(&ctx, &key).unwrap();
assert!(!iter.has_next());
}
#[test]
fn raw_storage_empty_and_missing_read_as_zero_length() {
let _guard = setup_runtime_test();
RawStorage::put(b"empty", b"");
let mut empty = [];
assert_eq!(
RawStorage::get_into(b"empty", &mut empty),
RawStorageGet::Found(0)
);
assert_eq!(
RawStorage::get_into(b"missing", &mut empty),
RawStorageGet::Found(0)
);
}
#[test]
fn raw_storage_i64_has_matches_neovm_zero_encoding() {
let _guard = setup_runtime_test();
RawStorage::put_i64_key(-7, 0);
assert_eq!(RawStorage::get_i64_key_or_zero(-7), 0);
assert!(RawStorage::has_i64_key(-7));
RawStorage::put_i64_key(-8, 1);
assert_eq!(RawStorage::get_i64_key_or_zero(-8), 1);
assert!(RawStorage::has_i64_key(-8));
}
#[test]
fn raw_key_builder_rejects_overflow_without_mutating_existing_key() {
let mut key = RawKeyBuilder::<4>::new();
assert!(key.push_bytes(b"ab"));
assert!(!key.push_bytes(b"cdef"));
assert_eq!(key.as_slice(), b"ab");
assert!(key.push_byte(b'c'));
assert!(key.push_byte(b'd'));
assert!(!key.push_byte(b'e'));
assert_eq!(key.as_slice(), b"abcd");
}
#[test]
fn storage_find_returns_struct_entries() {
let _guard = setup_runtime_test();
let ctx = NeoStorage::get_context().unwrap();
let prefix = NeoByteString::from_slice(b"market:");
let key_a = NeoByteString::from_slice(b"market:alpha");
let key_b = NeoByteString::from_slice(b"market:beta");
let val_a = NeoByteString::from_slice(b"one");
let val_b = NeoByteString::from_slice(b"two");
NeoStorage::put(&ctx, &key_a, &val_a).unwrap();
NeoStorage::put(&ctx, &key_b, &val_b).unwrap();
let mut iter = NeoStorage::find(&ctx, &prefix).unwrap();
let mut seen = Vec::new();
while iter.has_next() {
if let Some(entry) = iter.next() {
let st = entry.as_struct().expect("expected key/value struct");
let key_field = st
.get_field("key")
.and_then(NeoValue::as_byte_string)
.expect("missing key field");
let value_field = st
.get_field("value")
.and_then(NeoValue::as_byte_string)
.expect("missing value field");
seen.push((key_field.clone(), value_field.clone()));
}
}
assert_eq!(seen.len(), 2);
assert!(seen
.iter()
.any(|(k, v)| k.as_slice() == key_a.as_slice() && v.as_slice() == val_a.as_slice()));
assert!(seen
.iter()
.any(|(k, v)| k.as_slice() == key_b.as_slice() && v.as_slice() == val_b.as_slice()));
NeoStorage::delete(&ctx, &key_a).unwrap();
NeoStorage::delete(&ctx, &key_b).unwrap();
}
#[test]
fn storage_put_fails_for_read_only_context() {
let _guard = setup_runtime_test();
let ctx = NeoStorage::get_read_only_context().unwrap();
let key = NeoByteString::from_slice(b"demo");
let value = NeoByteString::from_slice(b"value");
let err = NeoStorage::put(&ctx, &key, &value).unwrap_err();
assert_eq!(err, NeoError::InvalidOperation);
}
#[test]
fn runtime_misc_helpers_work() {
let _guard = setup_runtime_test();
let account = NeoByteString::from_slice(b"account");
assert!(!NeoRuntime::check_witness(&account).unwrap().as_bool());
NeoVMSyscall::set_active_witnesses(std::slice::from_ref(&account)).unwrap();
assert!(NeoRuntime::check_witness(&account).unwrap().as_bool());
let event = NeoString::from_str("event");
let state = NeoArray::<NeoValue>::new();
assert!(NeoRuntime::notify(&event, &state).is_ok());
let message = NeoString::from_str("log");
assert!(NeoRuntime::log(&message).is_ok());
let platform = NeoRuntime::platform().unwrap();
assert!(!platform.as_str().is_empty());
}
#[test]
fn reset_host_state_clears_active_witnesses() {
let _guard = setup_runtime_test();
let account = NeoByteString::from_slice(b"account");
NeoVMSyscall::set_active_witnesses(std::slice::from_ref(&account)).unwrap();
assert!(NeoRuntime::check_witness(&account).unwrap().as_bool());
NeoVMSyscall::reset_host_state().unwrap();
assert!(!NeoRuntime::check_witness(&account).unwrap().as_bool());
}
#[test]
fn crypto_helpers_produce_deterministic_lengths() {
let _guard = setup_runtime_test();
let data = NeoByteString::from_slice(b"neo");
assert_eq!(NeoCrypto::sha256(&data).unwrap().len(), 32);
assert_eq!(NeoCrypto::ripemd160(&data).unwrap().len(), 20);
assert_eq!(NeoCrypto::keccak256(&data).unwrap().len(), 32);
assert_eq!(NeoCrypto::keccak512(&data).unwrap().len(), 64);
let seed = NeoInteger::new(42);
let murmur = NeoCrypto::murmur32(&data, seed).unwrap();
assert!(murmur.as_i32_saturating() != 0);
let signature = NeoByteString::from_slice(&[0x42; 64]);
let public_key = NeoByteString::from_slice(&[0x02; 33]);
assert!(NeoCrypto::verify_signature(&data, &signature, &public_key)
.unwrap()
.as_bool());
assert!(
NeoCrypto::verify_with_ecdsa(&data, &public_key, &signature, NeoInteger::new(1))
.unwrap()
.as_bool()
);
assert!(
!NeoCrypto::verify_with_ecdsa(&data, &public_key, &signature, NeoInteger::new(7))
.unwrap()
.as_bool()
);
}
#[test]
fn crypto_verification_argument_order_is_explicit() {
let _guard = setup_runtime_test();
let data = NeoByteString::from_slice(b"neo");
let signature = NeoByteString::from_slice(&[0xAA; 64]);
let public_key = NeoByteString::from_slice(&[0x02; 33]);
assert!(NeoCrypto::verify_signature(&data, &signature, &public_key)
.unwrap()
.as_bool());
assert!(!NeoCrypto::verify_signature(&data, &public_key, &signature)
.unwrap()
.as_bool());
assert!(
NeoCrypto::verify_with_ecdsa(&data, &public_key, &signature, NeoInteger::new(1))
.unwrap()
.as_bool()
);
assert!(
!NeoCrypto::verify_with_ecdsa(&data, &signature, &public_key, NeoInteger::new(1))
.unwrap()
.as_bool()
);
}
#[test]
fn host_active_contract_hash_controls_storage_partitioning() {
let _guard = setup_runtime_test();
let hash_a = NeoByteString::from_slice(&[0x11; 20]);
let hash_b = NeoByteString::from_slice(&[0x22; 20]);
let key = NeoByteString::from_slice(b"shared:key");
let val_a = NeoByteString::from_slice(b"value-a");
let val_b = NeoByteString::from_slice(b"value-b");
NeoVMSyscall::set_active_contract_hash(&hash_a).unwrap();
let ctx_a = NeoStorage::get_context().unwrap();
NeoStorage::put(&ctx_a, &key, &val_a).unwrap();
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), hash_a);
NeoVMSyscall::set_active_contract_hash(&hash_b).unwrap();
let ctx_b = NeoStorage::get_context().unwrap();
assert_eq!(NeoStorage::get(&ctx_b, &key).unwrap().len(), 0);
NeoStorage::put(&ctx_b, &key, &val_b).unwrap();
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), hash_b);
NeoVMSyscall::set_active_contract_hash(&hash_a).unwrap();
let ctx_a_again = NeoStorage::get_context().unwrap();
assert_eq!(NeoStorage::get(&ctx_a_again, &key).unwrap(), val_a);
NeoVMSyscall::set_active_contract_hash(&hash_b).unwrap();
let ctx_b_again = NeoStorage::get_context().unwrap();
assert_eq!(NeoStorage::get(&ctx_b_again, &key).unwrap(), val_b);
}
#[test]
fn runtime_exposes_independent_script_hash_context() {
let _guard = setup_runtime_test();
let calling = NeoByteString::from_slice(&[0xA1; 20]);
let entry = NeoByteString::from_slice(&[0xB2; 20]);
let executing = NeoByteString::from_slice(&[0xC3; 20]);
NeoVMSyscall::set_active_script_hashes(&calling, &entry, &executing).unwrap();
assert_eq!(NeoRuntime::get_calling_script_hash().unwrap(), calling);
assert_eq!(NeoRuntime::get_entry_script_hash().unwrap(), entry);
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), executing);
let updated_calling = NeoByteString::from_slice(&[0xD4; 20]);
NeoVMSyscall::set_active_calling_script_hash(&updated_calling).unwrap();
assert_eq!(
NeoRuntime::get_calling_script_hash().unwrap(),
updated_calling
);
assert_eq!(NeoRuntime::get_entry_script_hash().unwrap(), entry);
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), executing);
}
#[test]
fn nested_contract_invocation_preserves_entry_and_partitions_storage_by_executing_hash() {
let _guard = setup_runtime_test();
let calling = NeoByteString::from_slice(&[0x11; 20]);
let entry = NeoByteString::from_slice(&[0x22; 20]);
let root = NeoByteString::from_slice(&[0x33; 20]);
let child = NeoByteString::from_slice(&[0x44; 20]);
let grandchild = NeoByteString::from_slice(&[0x55; 20]);
let key = NeoByteString::from_slice(b"nested:partition:key");
let root_value = NeoByteString::from_slice(b"root");
let child_value = NeoByteString::from_slice(b"child");
let grandchild_value = NeoByteString::from_slice(b"grandchild");
NeoVMSyscall::set_active_script_hashes(&calling, &entry, &root).unwrap();
let root_ctx = NeoStorage::get_context().unwrap();
NeoStorage::put(&root_ctx, &key, &root_value).unwrap();
assert_eq!(NeoRuntime::get_calling_script_hash().unwrap(), calling);
assert_eq!(NeoRuntime::get_entry_script_hash().unwrap(), entry);
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), root);
NeoVMSyscall::begin_contract_invocation(&child).unwrap();
assert_eq!(NeoRuntime::get_calling_script_hash().unwrap(), root);
assert_eq!(NeoRuntime::get_entry_script_hash().unwrap(), entry);
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), child);
let child_ctx = NeoStorage::get_context().unwrap();
assert_eq!(NeoStorage::get(&child_ctx, &key).unwrap().len(), 0);
NeoStorage::put(&child_ctx, &key, &child_value).unwrap();
NeoVMSyscall::begin_contract_invocation(&grandchild).unwrap();
assert_eq!(NeoRuntime::get_calling_script_hash().unwrap(), child);
assert_eq!(NeoRuntime::get_entry_script_hash().unwrap(), entry);
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), grandchild);
let grandchild_ctx = NeoStorage::get_context().unwrap();
assert_eq!(NeoStorage::get(&grandchild_ctx, &key).unwrap().len(), 0);
NeoStorage::put(&grandchild_ctx, &key, &grandchild_value).unwrap();
NeoVMSyscall::end_contract_invocation().unwrap();
assert_eq!(NeoRuntime::get_calling_script_hash().unwrap(), root);
assert_eq!(NeoRuntime::get_entry_script_hash().unwrap(), entry);
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), child);
let child_ctx_again = NeoStorage::get_context().unwrap();
assert_eq!(
NeoStorage::get(&child_ctx_again, &key).unwrap(),
child_value
);
NeoVMSyscall::end_contract_invocation().unwrap();
assert_eq!(NeoRuntime::get_calling_script_hash().unwrap(), calling);
assert_eq!(NeoRuntime::get_entry_script_hash().unwrap(), entry);
assert_eq!(NeoRuntime::get_executing_script_hash().unwrap(), root);
let root_ctx_again = NeoStorage::get_context().unwrap();
assert_eq!(NeoStorage::get(&root_ctx_again, &key).unwrap(), root_value);
}