#![allow(dead_code)]
use neo_devpack_solidity::cli::compile_contracts;
use neo_devpack_solidity::runtime::{NeoRuntime, RuntimeConfig};
use proptest::prelude::*;
pub fn is_solidity_reserved(s: &str) -> bool {
matches!(
s,
"abstract"
| "after"
| "alias"
| "anonymous"
| "apply"
| "as"
| "assembly"
| "async"
| "auto"
| "bool"
| "break"
| "byte"
| "bytes"
| "case"
| "catch"
| "constant"
| "constructor"
| "continue"
| "contract"
| "copyof"
| "days"
| "default"
| "define"
| "delete"
| "do"
| "else"
| "emit"
| "enum"
| "error"
| "event"
| "external"
| "fallback"
| "false"
| "final"
| "for"
| "from"
| "function"
| "hex"
| "if"
| "immutable"
| "implements"
| "import"
| "in"
| "indexed"
| "inline"
| "instance"
| "interface"
| "internal"
| "is"
| "let"
| "library"
| "macro"
| "mapping"
| "match"
| "memory"
| "modifier"
| "mutable"
| "new"
| "null"
| "of"
| "override"
| "partial"
| "payable"
| "persistent"
| "pragma"
| "private"
| "promise"
| "public"
| "pure"
| "receive"
| "record"
| "reference"
| "relocatable"
| "return"
| "returns"
| "revert"
| "sealed"
| "seconds"
| "sizeof"
| "static"
| "storage"
| "string"
| "struct"
| "super"
| "supports"
| "switch"
| "temporary"
| "this"
| "throw"
| "true"
| "try"
| "type"
| "typedef"
| "typeof"
| "unchecked"
| "unicode"
| "using"
| "var"
| "view"
| "virtual"
| "weeks"
| "while"
| "wei"
| "years"
| "address"
| "fixed"
| "int"
| "int8"
| "int16"
| "int32"
| "int64"
| "int128"
| "int256"
| "uint"
| "uint8"
| "uint16"
| "uint32"
| "uint64"
| "uint128"
| "uint256"
| "bytes1"
| "bytes32"
)
}
pub fn identifier_strategy() -> impl Strategy<Value = String> {
"[a-zA-Z_][a-zA-Z0-9_]{0,30}".prop_filter("not a Solidity reserved keyword", |s| {
!is_solidity_reserved(s)
})
}
pub fn uint_value_strategy() -> impl Strategy<Value = String> {
prop_oneof![
Just("0".to_string()),
"[1-9][0-9]{0,20}".prop_map(String::from),
]
}
pub fn decode_uint_le(bytes: &[u8]) -> num_bigint::BigUint {
use num_bigint::BigUint;
if bytes.is_empty() {
BigUint::from(0u8)
} else {
BigUint::from_bytes_le(bytes)
}
}
pub fn decode_native_notification_state(data: &[u8]) -> Vec<serde_json::Value> {
let value: serde_json::Value = serde_json::from_slice(data).unwrap_or_else(|e| {
panic!(
"native notification data must be the emulator's JSON state encoding: {e}; raw=0x{}",
hex::encode(data)
)
});
assert_eq!(
value.get("type").and_then(|t| t.as_str()),
Some("Array"),
"native notification state must be an Array, got {value}"
);
value["value"]
.as_array()
.expect("native notification state array")
.clone()
}
pub fn native_state_bytes(item: &serde_json::Value) -> Vec<u8> {
assert_eq!(
item.get("type").and_then(|t| t.as_str()),
Some("ByteArray"),
"expected ByteArray state item, got {item}"
);
item["value"]
.as_array()
.expect("ByteArray value")
.iter()
.map(|b| b.as_u64().expect("byte") as u8)
.collect()
}
pub fn native_state_int(item: &serde_json::Value) -> i64 {
assert_eq!(
item.get("type").and_then(|t| t.as_str()),
Some("Integer"),
"expected Integer state item, got {item}"
);
item["value"].as_i64().expect("integer value")
}
pub fn native_state_is_null(item: &serde_json::Value) -> bool {
item.get("type").and_then(|t| t.as_str()) == Some("Null")
}
#[derive(Debug, PartialEq, Eq)]
pub enum ObservedBehavior {
Panicked(u8),
Returned(num_bigint::BigUint),
FaultOther(String),
}
pub fn compile_and_execute(source: &str) -> neo_devpack_solidity::runtime::ExecutionResult {
let artifacts = compile_contracts(source, false, 2)
.unwrap_or_else(|e| panic!("arith-scope compile failed: {:?}\nsource:\n{}", e, source));
assert!(
!artifacts.is_empty(),
"arith-scope compile produced no artifacts"
);
let mut runtime = NeoRuntime::new(RuntimeConfig::default())
.expect("arith-scope runtime construction must not fail");
runtime
.execute(&artifacts[0].bytecode, &[])
.expect("arith-scope execute must not fail at host level (a fault != host error)")
}
pub fn observe(result: &neo_devpack_solidity::runtime::ExecutionResult) -> ObservedBehavior {
if result.success {
return ObservedBehavior::Returned(decode_uint_le(&result.return_data));
}
let exc = match result.exception.as_ref() {
Some(e) => e,
None => return ObservedBehavior::FaultOther("no exception populated".to_string()),
};
if result.return_data.len() >= 36 && &result.return_data[..4] == &[0x4eu8, 0x48, 0x7b, 0x71] {
if result.return_data[4..35].iter().all(|b| *b == 0) {
return ObservedBehavior::Panicked(result.return_data[35]);
}
}
let msg = &exc.message;
if let Some(idx) = msg.find("Panic: 0x") {
let tail = &msg[idx + "Panic: 0x".len()..];
let hex_part: String = tail
.chars()
.take_while(|c| c.is_ascii_hexdigit())
.take(2)
.collect();
if !hex_part.is_empty() {
if let Ok(sig) = u8::from_str_radix(&hex_part, 16) {
return ObservedBehavior::Panicked(sig);
}
}
}
ObservedBehavior::FaultOther(msg.clone())
}