neo-devpack-solidity 0.22.0

Production-focused Solidity-to-NeoVM compilation system
Documentation
#[test]
fn syscalls_script_hash_to_address_keeps_precise_contract_permissions() {
    let source = r#"
    pragma solidity ^0.8.20;

    interface ICallee {
        function foo(uint256 x) external view returns (uint256);
    }

    contract UsesScriptHashConversions {
        bytes20 constant CALLEE_HASH_LE = hex"14131211100f0e0d0c0b0a090807060504030201";

        function run(uint256 x) public view returns (uint256) {
            return ICallee(Syscalls.scriptHashToAddress(CALLEE_HASH_LE)).foo(x);
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    assert_eq!(artifacts.len(), 1);

    let manifest = &artifacts[0].manifest;
    let permissions = manifest["permissions"]
        .as_array()
        .expect("permissions array");
    let contract = "0x0102030405060708090a0b0c0d0e0f1011121314";

    assert!(
        permissions.iter().any(|entry| {
            entry["contract"] == Value::String(contract.into())
                && entry["methods"]
                    .as_array()
                    .is_some_and(|methods| methods.iter().any(|m| m == "foo"))
        }),
        "expected exact contract permission for scriptHashToAddress static target"
    );

    assert!(
        permissions
            .iter()
            .all(|entry| entry["contract"] != Value::String("*".into())),
        "scriptHashToAddress static target should not require wildcard permissions"
    );
}

#[test]
fn syscalls_contract_call_static_target_emits_exact_permissions() {
    let source = r#"
    pragma solidity ^0.8.19;

    contract CallsNativeViaContractCall {
        function callGasSupply() public returns (bytes memory) {
            return Syscalls.contractCall(
                NativeCalls.GAS_CONTRACT,
                "totalSupply",
                abi.encode()
            );
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    assert_eq!(artifacts.len(), 1);

    let manifest = &artifacts[0].manifest;
    let permissions = manifest["permissions"]
        .as_array()
        .expect("permissions array");

    let gas_hash_le = super::bytecode::native_contract_hash(neo_devpack_solidity::ir::NativeContract::Gas);
    let gas_hash_be: Vec<u8> = gas_hash_le.iter().rev().copied().collect();
    let gas_contract = format!("0x{}", hex::encode(gas_hash_be));

    let stdlib_hash_le =
        super::bytecode::native_contract_hash(neo_devpack_solidity::ir::NativeContract::StdLib);
    let stdlib_hash_be: Vec<u8> = stdlib_hash_le.iter().rev().copied().collect();
    let stdlib_contract = format!("0x{}", hex::encode(stdlib_hash_be));

    assert!(
        permissions.iter().any(|entry| {
            entry["contract"] == gas_contract
                && entry["methods"]
                    .as_array()
                    .is_some_and(|methods| methods.iter().any(|m| m == "totalSupply"))
        }),
        "expected GAS.totalSupply permission in manifest"
    );

    assert!(
        permissions.iter().any(|entry| {
            entry["contract"] == stdlib_contract
                && entry["methods"].as_array().is_some_and(|methods| {
                    methods.iter().any(|m| m == "serialize")
                        && methods.iter().any(|m| m == "deserialize")
                })
        }),
        "expected StdLib.serialize + StdLib.deserialize permissions for Syscalls.contractCall"
    );

    assert!(
        permissions
            .iter()
            .all(|entry| entry["contract"] != Value::String("*".into())),
        "static Syscalls.contractCall should not require wildcard permissions"
    );
}

#[test]
fn external_contract_call_static_target_emits_exact_permissions() {
    let source = r#"
    pragma solidity ^0.8.19;

    interface IFace {
        function ping(uint256 value) external returns (uint256);
    }

    contract ExternalCallPermissionHarness {
        function callPing(uint256 value) public returns (uint256) {
            return IFace(0x0102030405060708090a0b0c0d0e0f1011121314).ping(value);
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    assert_eq!(artifacts.len(), 1);

    let manifest = &artifacts[0].manifest;
    let permissions = manifest["permissions"]
        .as_array()
        .expect("permissions array");

    let contract = "0x0102030405060708090a0b0c0d0e0f1011121314";

    assert!(
        permissions.iter().any(|entry| {
            entry["contract"] == Value::String(contract.into())
                && entry["methods"]
                    .as_array()
                    .is_some_and(|methods| methods.iter().any(|m| m == "ping"))
        }),
        "expected exact contract permission for static target address"
    );

    assert!(
        permissions
            .iter()
            .all(|entry| entry["contract"] != Value::String("*".into())),
        "static target should not require wildcard contract permissions"
    );
}

#[test]
fn native_contracts_member_call_emits_exact_native_permission() {
    let source = [
        include_str!("../../../../devpack/contracts/NativeContracts.sol"),
        r#"
        pragma solidity ^0.8.19;

        contract NativeContractsPermissionHarness {
            function callGasSupply() public view returns (uint256) {
                return NativeContracts.GAS_CONTRACT.totalSupply();
            }
        }
        "#,
    ]
    .join("\n");

    let artifacts = compile_contracts(&source, false, 2).expect("compilation failed");
    let harness = artifacts
        .iter()
        .find(|artifact| artifact.metadata.name == "NativeContractsPermissionHarness")
        .expect("expected NativeContractsPermissionHarness artifact");

    let permissions = harness.manifest["permissions"]
        .as_array()
        .expect("permissions array");

    let gas_hash_le = super::bytecode::native_contract_hash(neo_devpack_solidity::ir::NativeContract::Gas);
    let gas_hash_be: Vec<u8> = gas_hash_le.iter().rev().copied().collect();
    let gas_contract = format!("0x{}", hex::encode(gas_hash_be));

    assert!(
        permissions.iter().any(|entry| {
            entry["contract"] == gas_contract
                && entry["methods"]
                    .as_array()
                    .is_some_and(|methods| methods.iter().any(|m| m == "totalSupply"))
        }),
        "expected GAS.totalSupply permission in manifest for NativeContracts member call"
    );

    assert!(
        permissions
            .iter()
            .all(|entry| entry["contract"] != Value::String("*".into())),
        "NativeContracts member call should not require wildcard contract permissions"
    );
}

#[test]
fn repeated_static_native_contract_calls_emit_single_deduplicated_permission_entry() {
    let source = r#"
    pragma solidity ^0.8.19;

    contract PermissionDedupHarness {
        function run() public returns (bytes memory, bytes memory, bytes memory) {
            bytes memory a = Syscalls.contractCall(
                NativeCalls.GAS_CONTRACT,
                "totalSupply",
                abi.encode()
            );
            bytes memory b = Syscalls.contractCall(
                NativeCalls.GAS_CONTRACT,
                "balanceOf",
                abi.encode(address(this))
            );
            bytes memory c = Syscalls.contractCall(
                NativeCalls.GAS_CONTRACT,
                "totalSupply",
                abi.encode()
            );
            return (a, b, c);
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    assert_eq!(artifacts.len(), 1);

    let manifest = &artifacts[0].manifest;
    let permissions = manifest["permissions"]
        .as_array()
        .expect("permissions array");

    let gas_hash_le = super::bytecode::native_contract_hash(neo_devpack_solidity::ir::NativeContract::Gas);
    let gas_hash_be: Vec<u8> = gas_hash_le.iter().rev().copied().collect();
    let gas_contract = format!("0x{}", hex::encode(gas_hash_be));

    let gas_entries: Vec<&Value> = permissions
        .iter()
        .filter(|entry| entry["contract"] == Value::String(gas_contract.clone()))
        .collect();
    assert_eq!(
        gas_entries.len(),
        1,
        "expected exactly one GAS permission entry, got: {gas_entries:?}"
    );

    let gas_methods = gas_entries[0]["methods"].as_array().expect("GAS methods array");
    let gas_method_names: Vec<&str> = gas_methods.iter().filter_map(|m| m.as_str()).collect();
    assert_eq!(
        gas_method_names,
        vec!["balanceOf", "totalSupply"],
        "expected deduplicated, deterministic method set"
    );

    assert!(
        permissions
            .iter()
            .all(|entry| entry["contract"] != Value::String("*".into())),
        "static native calls should not require wildcard contract permissions"
    );
}