neo-devpack-solidity 0.22.0

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

    contract Payments {
        function sendTo(address payable to, uint256 amount) public returns (bool) {
            return to.send(amount);
        }

        function transferTo(address payable to, uint256 amount) public {
            to.transfer(amount);
        }
    }
    "#;

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

    let manifest = &artifacts[0].manifest;
    assert!(
        manifest
            .get("features")
            .and_then(Value::as_object)
            .is_some_and(|features| features.is_empty()),
        "expected `manifest.features` to be an empty object for Neo N3 compatibility"
    );

    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));

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

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

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

    contract UsesNativeCalls {
        function transferGas(address from, address to, uint256 amount, bytes memory data) public returns (bool) {
            return NativeCalls.gasTransfer(from, to, amount, data);
        }
    }
    "#;

    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));

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

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

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

    contract UsesNativeConstants {
        function supply() public view returns (uint256) {
            return NativeCalls.GAS_CONTRACT.totalSupply();
        }
    }
    "#;

    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));

    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()
            .all(|entry| entry["contract"] != Value::String("*".into())),
        "native contract calls with known hashes should not require wildcard permissions"
    );
}

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

    contract DeployHarness {
        function deploy(bytes memory nef, bytes memory manifest) public returns (address) {
            return NativeCalls.deployContract(nef, manifest);
        }
    }
    "#;

    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 cm_hash_le =
        super::bytecode::native_contract_hash(neo_devpack_solidity::ir::NativeContract::ContractManagement);
    let cm_hash_be: Vec<u8> = cm_hash_le.iter().rev().copied().collect();
    let cm_contract = format!("0x{}", hex::encode(cm_hash_be));

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

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

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

    contract GetContractHarness {
        function manifestBytes(address hash) public view returns (bytes memory) {
            NativeCalls.ContractState memory state = NativeCalls.getContract(hash);
            return state.manifest;
        }
    }
    "#;

    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 cm_hash_le =
        super::bytecode::native_contract_hash(neo_devpack_solidity::ir::NativeContract::ContractManagement);
    let cm_hash_be: Vec<u8> = cm_hash_le.iter().rev().copied().collect();
    let cm_contract = format!("0x{}", hex::encode(cm_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"] == cm_contract
                && entry["methods"]
                    .as_array()
                    .is_some_and(|methods| methods.iter().any(|m| m == "getContract"))
        }),
        "expected ContractManagement.getContract 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"))
        }),
        "expected StdLib.serialize permission in manifest"
    );

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