use neo_devpack_solidity::cli::compile_contracts;
use neo_devpack_solidity::ir::builtin_intrinsic_surface;
fn unsupported_builtin_msg(base: &str, member: &str) -> String {
format!("unsupported builtin library call '{base}.{member}'")
}
fn compile_err_text(src: &str) -> Option<String> {
match compile_contracts(src, false, 2) {
Ok(_) => None,
Err(e) => Some(format!("{e:?}")),
}
}
#[test]
fn syscalls_hasrole_is_clean_unsupported_builtin_error() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function f(bytes1 role, bytes memory pk) external view returns (bool) {
return Syscalls.hasRole(role, pk);
}
}"#;
let err = compile_err_text(src)
.expect("Syscalls.hasRole must be a compile error (Neo N3 has no hasRole intrinsic)");
assert!(
err.contains(&unsupported_builtin_msg("Syscalls", "hasRole")),
"Syscalls.hasRole must fail with the targeted unsupported-builtin \
diagnostic; got: {err}"
);
assert!(
!err.contains("hasRole,") && !err.contains(", hasRole"),
"the supported-intrinsics list must not advertise 'hasRole'; got: {err}"
);
}
#[test]
fn runtime_hasrole_is_clean_unsupported_builtin_error() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function f(address a, bytes32 role) external view returns (bool) {
return Runtime.hasRole(a, role);
}
}"#;
let err = compile_err_text(src)
.expect("Runtime.hasRole must be a compile error (removed devpack surface)");
assert!(
err.contains(&unsupported_builtin_msg("Runtime", "hasRole")),
"Runtime.hasRole must fail with the targeted unsupported-builtin \
diagnostic; got: {err}"
);
}
#[test]
fn get_designated_by_role_still_lowers() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract C {
function viaSyscalls(bytes1 role, uint256 index) external view returns (bytes[] memory) {
return Syscalls.getDesignatedByRole(role, index);
}
function viaNativeCalls(bytes1 role, uint256 index) external view returns (bytes[] memory) {
return NativeCalls.getDesignatedByRole(role, index);
}
}"#;
if let Some(err) = compile_err_text(src) {
panic!("getDesignatedByRole must keep compiling: {err}");
}
}
#[test]
fn user_defined_hasrole_methods_are_unaffected() {
let src = r#"// SPDX-License-Identifier: MIT
pragma solidity ^0.8.19;
contract AccessControl {
mapping(bytes32 => mapping(address => bool)) private _roles;
function hasRole(bytes32 role, address account) public view returns (bool) {
return _roles[role][account];
}
function grantRole(bytes32 role, address account) external {
_roles[role][account] = true;
}
function check(bytes32 role) external view returns (bool) {
return hasRole(role, msg.sender);
}
}"#;
if let Some(err) = compile_err_text(src) {
panic!("user-defined hasRole must keep compiling: {err}");
}
}
#[test]
fn devpack_sources_do_not_declare_hasrole() {
for rel in [
"devpack/libraries/Runtime.sol",
"devpack/contracts/Syscalls.sol",
"devpack/contracts/NativeCalls.sol",
] {
let path = format!("{}/{rel}", env!("CARGO_MANIFEST_DIR"));
let text = std::fs::read_to_string(&path).unwrap_or_else(|e| panic!("read {path}: {e}"));
assert!(
!text.contains("function hasRole"),
"{rel} must not declare `function hasRole` — builtin devpack \
libraries are compiler intrinsics; a declared-but-unlowered \
member is an uncallable surface (and the removed implementation \
ignored `role`, silently degrading to checkWitness)"
);
}
}
fn probe_source(base: &str, member: &str, arity: usize) -> String {
let params = (0..arity)
.map(|i| format!("bytes memory a{i}"))
.collect::<Vec<_>>()
.join(", ");
let args = (0..arity)
.map(|i| format!("a{i}"))
.collect::<Vec<_>>()
.join(", ");
format!(
"// SPDX-License-Identifier: MIT\n\
pragma solidity ^0.8.19;\n\
contract Probe {{\n\
function probe({params}) public {{\n\
{base}.{member}({args});\n\
}}\n\
}}"
)
}
#[test]
fn every_whitelisted_builtin_member_lowers() {
let mut uncallable: Vec<String> = Vec::new();
for (base, members) in builtin_intrinsic_surface() {
for member in members {
let mut lowered = false;
let mut last_err = String::new();
for arity in 0..=4usize {
let src = probe_source(base, member, arity);
match compile_err_text(&src) {
None => {
lowered = true;
break;
}
Some(err) => {
if !err.contains(&unsupported_builtin_msg(base, member)) {
lowered = true;
break;
}
last_err = err;
}
}
}
if !lowered {
uncallable.push(format!("{base}.{member} (last error: {last_err})"));
}
}
}
assert!(
uncallable.is_empty(),
"builtin whitelist members without a lowering (uncallable intrinsics \
— remove them from builtin_library_supported_members in \
src/ir/context/builtins/resolve.rs or implement their lowering):\n{}",
uncallable.join("\n")
);
}
#[test]
fn builtin_whitelists_have_no_duplicate_members() {
for (base, members) in builtin_intrinsic_surface() {
let mut seen = std::collections::HashSet::new();
let dupes: Vec<&str> = members
.iter()
.copied()
.filter(|m| !seen.insert(*m))
.collect();
assert!(
dupes.is_empty(),
"duplicate members in the '{base}' builtin whitelist: {dupes:?}"
);
}
}