#![allow(unused_imports)]
#![allow(clippy::uninlined_format_args)]
use super::common::*;
use neo_devpack_solidity::cli::compile_contracts;
use neo_devpack_solidity::runtime::types::StackItem;
use neo_devpack_solidity::runtime::{NeoRuntime, RuntimeConfig};
use proptest::prelude::*;
fn known_selector() -> [u8; 4] {
use sha3::{Digest, Keccak256};
let mut h = Keccak256::new();
h.update(b"known()");
let d = h.finalize();
[d[0], d[1], d[2], d[3]]
}
fn manifest_has_method(manifest: &serde_json::Value, name: &str) -> bool {
manifest
.get("abi")
.and_then(|a| a.get("methods"))
.and_then(|m| m.as_array())
.map(|arr| {
arr.iter()
.any(|m| m.get("name").and_then(serde_json::Value::as_str) == Some(name))
})
.unwrap_or(false)
}
proptest! {
#![proptest_config(ProptestConfig::with_cases(48))]
#[test]
fn fallback_dispatched_on_unknown_selector(
sel in any::<[u8; 4]>(),
payload in prop::collection::vec(any::<u8>(), 0..=8),
) {
prop_assume!(sel != known_selector());
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function known() external pure returns (uint256) { return 1; }
fallback() external returns (uint256) { return 0xfa11; }
}"#;
let arts = compile_contracts(src, false, 2)
.map_err(|e| TestCaseError::fail(format!("compile failed: {:?}", e)))?;
prop_assert_eq!(arts.len(), 1);
let art = &arts[0];
prop_assert!(
manifest_has_method(&art.manifest, "fallback"),
"compiled manifest is missing the `fallback` method entry; \
abi.methods={:?}",
art.manifest.get("abi").and_then(|a| a.get("methods"))
);
prop_assert!(
manifest_has_method(&art.manifest, "known"),
"compiled manifest is missing the `known` method entry"
);
let mut rt = NeoRuntime::new(RuntimeConfig::default())
.map_err(|e| TestCaseError::fail(format!("rt new: {:?}", e)))?;
let mut cd = Vec::with_capacity(4 + payload.len());
cd.extend_from_slice(&sel);
cd.extend_from_slice(&payload);
let r = rt
.call_method(
&art.bytecode,
&art.tokens,
&art.manifest,
"fallback",
&[StackItem::byte_array(cd.clone())],
)
.map_err(|e| TestCaseError::fail(format!("call_method fallback: {:?}", e)))?;
prop_assert!(
r.success,
"fallback dispatch must succeed for sel={} payload={}; exc={:?}",
hex::encode(sel),
hex::encode(&payload),
r.exception.as_ref().map(|e| &e.message)
);
let v = decode_uint_le(&r.return_data);
prop_assert_eq!(
v.clone(),
num_bigint::BigUint::from(0xfa11u32),
"fallback must return 0xfa11; got {} (rd_hex={}, sel={}, payload={})",
v,
hex::encode(&r.return_data),
hex::encode(sel),
hex::encode(&payload)
);
}
}
#[test]
fn receive_dispatched_on_empty_calldata() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
uint256 public lastCall;
receive() external payable { lastCall = 1; }
fallback() external { lastCall = 2; }
}"#;
let arts =
compile_contracts(src, false, 2).unwrap_or_else(|e| panic!("compile failed: {:?}", e));
assert_eq!(arts.len(), 1);
let art = &arts[0];
assert!(
manifest_has_method(&art.manifest, "onNEP17Payment"),
"expected `receive()` to be lowered as `onNEP17Payment`; abi.methods={:?}",
art.manifest.get("abi").and_then(|a| a.get("methods"))
);
assert!(
manifest_has_method(&art.manifest, "fallback"),
"expected `fallback` method entry; abi.methods={:?}",
art.manifest.get("abi").and_then(|a| a.get("methods"))
);
{
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("rt(receive)");
let r = rt
.call_method(
&art.bytecode,
&art.tokens,
&art.manifest,
"onNEP17Payment",
&[
StackItem::byte_array(vec![0u8; 20]),
StackItem::Integer(0),
StackItem::Null,
],
)
.expect("call onNEP17Payment");
assert!(
r.success,
"onNEP17Payment (receive lowering) must succeed; exc={:?}",
r.exception.as_ref().map(|e| &e.message)
);
let r2 = rt
.call_method(&art.bytecode, &art.tokens, &art.manifest, "lastCall", &[])
.expect("call lastCall after receive");
assert!(
r2.success,
"lastCall getter must succeed; exc={:?}",
r2.exception.as_ref().map(|e| &e.message)
);
let v = decode_uint_le(&r2.return_data);
assert_eq!(
v,
num_bigint::BigUint::from(1u32),
"after receive() lastCall must be 1; got {} (rd_hex={}). \
If 2, the fallback body fired instead of the receive body — \
receive→onNEP17Payment lowering is aliased onto fallback.",
v,
hex::encode(&r2.return_data)
);
}
{
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("rt(fallback)");
let r = rt
.call_method(
&art.bytecode,
&art.tokens,
&art.manifest,
"fallback",
&[StackItem::byte_array(vec![0xAB])],
)
.expect("call fallback (1-byte)");
assert!(
r.success,
"fallback (1-byte calldata) must succeed; exc={:?}",
r.exception.as_ref().map(|e| &e.message)
);
let r2 = rt
.call_method(&art.bytecode, &art.tokens, &art.manifest, "lastCall", &[])
.expect("call lastCall after fallback(1B)");
assert!(r2.success);
let v = decode_uint_le(&r2.return_data);
assert_eq!(
v,
num_bigint::BigUint::from(2u32),
"after fallback() (1-byte calldata) lastCall must be 2; got {} (rd_hex={}). \
If 1, fallback was aliased to receive — the < 4-byte calldata \
rule must NOT route to receive.",
v,
hex::encode(&r2.return_data)
);
}
{
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("rt(fallback4)");
let r = rt
.call_method(
&art.bytecode,
&art.tokens,
&art.manifest,
"fallback",
&[StackItem::byte_array(vec![0xDE, 0xAD, 0xBE, 0xEF])],
)
.expect("call fallback (4-byte unknown selector)");
assert!(
r.success,
"fallback (4-byte unknown selector) must succeed; exc={:?}",
r.exception.as_ref().map(|e| &e.message)
);
let r2 = rt
.call_method(&art.bytecode, &art.tokens, &art.manifest, "lastCall", &[])
.expect("call lastCall after fallback(4B)");
assert!(r2.success);
let v = decode_uint_le(&r2.return_data);
assert_eq!(
v,
num_bigint::BigUint::from(2u32),
"after fallback() (4-byte unknown selector) lastCall must be 2; got {} (rd_hex={}). \
If 1, an unknown selector incorrectly fell through to receive.",
v,
hex::encode(&r2.return_data)
);
}
}
#[test]
fn known_selector_overrides_fallback() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
uint256 public lastCall;
receive() external payable { lastCall = 1; }
fallback() external { lastCall = 2; }
function setIt(uint256 v) external { lastCall = v; }
}"#;
let arts =
compile_contracts(src, false, 2).unwrap_or_else(|e| panic!("compile failed: {:?}", e));
let art = &arts[0];
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("rt");
let r = rt
.call_method(
&art.bytecode,
&art.tokens,
&art.manifest,
"setIt",
&[StackItem::Integer(0x1234)],
)
.expect("call setIt");
assert!(
r.success,
"setIt must succeed; exc={:?}",
r.exception.as_ref().map(|e| &e.message)
);
let r2 = rt
.call_method(&art.bytecode, &art.tokens, &art.manifest, "lastCall", &[])
.expect("call lastCall getter");
assert!(
r2.success,
"lastCall getter must succeed; exc={:?}",
r2.exception.as_ref().map(|e| &e.message)
);
let v = decode_uint_le(&r2.return_data);
assert_eq!(
v,
num_bigint::BigUint::from(0x1234u32),
"lastCall() getter must return the stored value (0x1234); got {} \
(rd_hex={}). If 1 or 2, the named-selector dispatch was \
overridden by the receive/fallback path.",
v,
hex::encode(&r2.return_data)
);
}
#[test]
fn revert_when_no_fallback_no_receive() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function only() external pure returns (uint256) { return 7; }
}"#;
let arts =
compile_contracts(src, false, 2).unwrap_or_else(|e| panic!("compile failed: {:?}", e));
let art = &arts[0];
assert!(
!manifest_has_method(&art.manifest, "fallback"),
"no-hook contract must not synthesize a fallback; abi.methods={:?}",
art.manifest.get("abi").and_then(|a| a.get("methods"))
);
assert!(
!manifest_has_method(&art.manifest, "receive"),
"no-hook contract must not synthesize a receive entry"
);
assert!(
!manifest_has_method(&art.manifest, "onNEP17Payment"),
"no-hook contract must not synthesize an onNEP17Payment entry"
);
let mut rt = NeoRuntime::new(RuntimeConfig::default()).expect("rt");
let r_ok = rt
.call_method(&art.bytecode, &art.tokens, &art.manifest, "only", &[])
.expect("call only()");
assert!(
r_ok.success,
"only() must succeed; exc={:?}",
r_ok.exception.as_ref().map(|e| &e.message)
);
let v = decode_uint_le(&r_ok.return_data);
assert_eq!(
v,
num_bigint::BigUint::from(7u32),
"only() must return 7; got {} (rd_hex={})",
v,
hex::encode(&r_ok.return_data)
);
let r_recv = rt.call_method(&art.bytecode, &art.tokens, &art.manifest, "receive", &[]);
assert!(
r_recv.is_err(),
"calling `receive` on a no-hook contract must surface Err(_); \
instead got Ok({:?}). The runtime is silently absorbing empty \
calldata where Solidity's spec requires a revert.",
r_recv.as_ref().ok()
);
let r_unknown = rt.call_method(
&art.bytecode,
&art.tokens,
&art.manifest,
"fallback",
&[StackItem::byte_array(vec![0xDE, 0xAD, 0xBE, 0xEF])],
);
assert!(
r_unknown.is_err(),
"calling `fallback` on a no-hook contract must surface Err(_); \
instead got Ok({:?}). If a fallback offset materialized despite \
no source-level fallback, the lowering is over-synthesizing hooks.",
r_unknown.as_ref().ok()
);
}
#[test]
fn fe1_receive_with_explicit_onnep17_is_a_hard_error() {
let src = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract Both {
/// @dev This is intentionally dead: Neo N3 only invokes onNEP17Payment.
receive() external payable {
// never fires on Neo
}
function onNEP17Payment(address from, uint256 amount, Any data) external {
// the real callback
}
}
"#;
let result = compile_contracts(src, false, 2);
assert!(
result.is_err(),
"M-FE1: a contract defining both receive() and onNEP17Payment must be \
a hard error (receive() would be silently dead code); compile \
unexpectedly succeeded"
);
}
#[test]
fn fe1_receive_without_onnep17_still_compiles() {
let src = r#"
// SPDX-License-Identifier: MIT
pragma solidity ^0.8.20;
contract OnlyReceive {
receive() external payable {}
}
"#;
let result = compile_contracts(src, false, 2);
assert!(
result.is_ok(),
"receive() without an explicit onNEP17Payment must still compile (it \
is remapped); got error: {:?}",
result.err()
);
}