use std::process::Command;
use neo_devpack_solidity::runtime::execution::ExecutionContext;
use neo_devpack_solidity::runtime::RuntimeConfig;
use sha2::{Digest, Sha256};
fn syscall_id(name: &str) -> [u8; 4] {
let mut h = Sha256::new();
h.update(name.as_bytes());
let d = h.finalize();
[d[0], d[1], d[2], d[3]]
}
fn push_data(script: &mut Vec<u8>, data: &[u8]) {
assert!(data.len() <= u8::MAX as usize, "push_data length overflow");
script.push(0x0C); script.push(data.len() as u8);
script.extend_from_slice(data);
}
fn run_to_fault(code: &[u8]) -> Option<String> {
let mut ctx = ExecutionContext::new(&RuntimeConfig::default()).expect("context init");
ctx.initialize(code, &[]).expect("init");
loop {
match ctx.step() {
Ok(state) => {
if state.halted {
return None;
}
}
Err(err) => return Some(err.to_string()),
}
}
}
#[test]
fn storage_local_get_faults_as_unknown_syscall() {
let mut code = vec![];
push_data(&mut code, b"some_key");
code.push(0x41); code.extend_from_slice(&syscall_id("System.Storage.Local.Get"));
code.push(0x40);
let fault = run_to_fault(&code).expect("System.Storage.Local.Get must FAULT");
assert!(
fault.contains("Unsupported syscall"),
"expected unknown-syscall fault, got: {fault}"
);
}
#[test]
fn all_storage_local_syscalls_fault_as_unknown() {
for name in [
"System.Storage.Local.Get",
"System.Storage.Local.Put",
"System.Storage.Local.Delete",
"System.Storage.Local.Find",
] {
let mut code = vec![];
push_data(&mut code, b"value");
push_data(&mut code, b"key");
code.push(0x41); code.extend_from_slice(&syscall_id(name));
code.push(0x40);
let fault =
run_to_fault(&code).unwrap_or_else(|| panic!("{name} must FAULT, but it halted"));
assert!(
fault.contains("Unsupported syscall"),
"{name}: expected unknown-syscall fault, got: {fault}"
);
}
}
#[test]
fn real_storage_get_still_works() {
let mut code = vec![];
code.push(0x41); code.extend_from_slice(&syscall_id("System.Storage.GetContext"));
push_data(&mut code, b"missing_key");
code.push(0x41); code.extend_from_slice(&syscall_id("System.Storage.Get"));
code.push(0x40);
assert!(
run_to_fault(&code).is_none(),
"System.Storage.Get must still execute"
);
}
#[test]
fn compiler_rejects_local_storage_intrinsics() {
let compiler = std::path::PathBuf::from(env!("CARGO_BIN_EXE_neo-solc"));
assert!(compiler.exists(), "Compiler not found");
let cases = [
(
"syscalls_storage_get_local",
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract LocalSyscallUser {
function readLocal(bytes memory key) public view returns (bytes memory) {
return Syscalls.storageGetLocal(key);
}
}
"#,
),
(
"storage_get_local",
r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract LocalStorageUser {
function readLocal(bytes memory key) public view returns (bytes memory) {
return Storage.getLocal(key);
}
}
"#,
),
];
for (label, source) in cases {
let dir = std::env::temp_dir().join(format!(
"neo-sol-gap-runtime-local-{label}-{}",
std::process::id()
));
std::fs::create_dir_all(&dir).expect("create temp dir");
let src_path = dir.join("LocalUser.sol");
std::fs::write(&src_path, source).expect("write source");
let output = Command::new(&compiler)
.arg(&src_path)
.arg("-o")
.arg(dir.join("LocalUser"))
.output()
.expect("run compiler");
let combined = format!(
"{}{}",
String::from_utf8_lossy(&output.stdout),
String::from_utf8_lossy(&output.stderr)
);
assert!(
!output.status.success(),
"{label}: compilation must fail, but it succeeded. Output:\n{combined}"
);
assert!(
!combined.contains("Storage.Local"),
"{label}: diagnostics must not advertise the removed syscalls. Output:\n{combined}"
);
let _ = std::fs::remove_dir_all(&dir);
}
}