neo-devpack-solidity 0.22.0

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

    contract StructHarness {
        struct S {
            uint256 a;
            uint256 b;
        }

        mapping(address => S) private m;
        S private s;

        function run() public returns (uint256) {
            S storage ref1 = m[msg.sender];
            ref1.a = 7;
            ref1.b = 9;

            s = S({a: 1, b: 2});

            S memory tmp = m[msg.sender];
            return tmp.a + tmp.b + s.a + s.b;
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    let result = execute_bytecode(&artifacts[0].bytecode);
    let failure = result
        .exception
        .as_ref()
        .map(|ex| ex.message.as_str())
        .unwrap_or("<no exception>");
    assert!(
        result.is_success(),
        "expected struct harness to succeed, got: {failure}"
    );
    assert_eq!(result.return_data, 19i64.to_le_bytes().to_vec());
}

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

    contract NestedStructHarness {
        struct Inner {
            uint256 x;
            bool y;
        }

        struct Outer {
            Inner inner;
            uint256 z;
        }

        mapping(address => Outer) private m;
        Outer private s;

        function run() public returns (uint256) {
            Outer storage o = m[msg.sender];
            o.inner.x = 7;
            o.inner.y = true;
            o.z = 3;

            s.inner.x = 1;
            s.inner.y = false;
            s.z = 2;

            Outer memory tmp = m[msg.sender];
            return tmp.inner.x + (tmp.inner.y ? 1 : 0) + tmp.z
                + s.inner.x + (s.inner.y ? 1 : 0) + s.z;
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    let result = execute_bytecode(&artifacts[0].bytecode);
    let failure = result
        .exception
        .as_ref()
        .map(|ex| ex.message.as_str())
        .unwrap_or("<no exception>");
    assert!(
        result.is_success(),
        "expected nested struct harness to succeed, got: {failure}"
    );
    assert_eq!(result.return_data, 14i64.to_le_bytes().to_vec());
}

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

    contract DeepNestedStructHarness {
        struct Leaf {
            uint256 a;
        }

        struct Inner {
            Leaf leaf;
            uint256 x;
        }

        struct Outer {
            Inner inner;
            uint256 z;
        }

        mapping(address => Outer) private m;
        Outer private s;

        function run() public returns (uint256) {
            Outer storage o = m[msg.sender];
            o.inner = Inner({ leaf: Leaf({ a: 7 }), x: 9 });
            o.z = 3;

            s = Outer({ inner: Inner({ leaf: Leaf({ a: 1 }), x: 2 }), z: 4 });

            Outer memory tmp = m[msg.sender];
            return tmp.inner.leaf.a + tmp.inner.x + tmp.z
                + s.inner.leaf.a + s.inner.x + s.z;
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    let result = execute_bytecode(&artifacts[0].bytecode);
    let failure = result
        .exception
        .as_ref()
        .map(|ex| ex.message.as_str())
        .unwrap_or("<no exception>");
    assert!(
        result.is_success(),
        "expected deep nested struct harness to succeed, got: {failure}"
    );
    assert_eq!(result.return_data, 26i64.to_le_bytes().to_vec());
}

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

    contract DeleteHarness {
        mapping(bytes32 => bytes32) private mBytes;
        mapping(bytes32 => address) private mAddr;

        struct S {
            address a;
            uint256 b;
            bool c;
            bytes32 d;
        }

        mapping(bytes32 => S) private mStruct;

        function run() public returns (uint256) {
            bytes32 k1 = bytes32(uint256(1));
            bytes32 k2 = bytes32(uint256(2));

            mBytes[k1] = bytes32(uint256(9));
            mAddr[k1] = msg.sender;

            S storage s = mStruct[k1];
            s.a = msg.sender;
            s.b = 7;
            s.c = true;
            s.d = bytes32(uint256(11));

            delete mBytes[k1];
            delete mAddr[k1];
            delete mStruct[k1];

            uint256 ok = 0;

            if (mBytes[k1] == bytes32(0)) ok += 1;
            if (mAddr[k1] == address(0)) ok += 2;
            S memory afterDelete = mStruct[k1];
            if (afterDelete.a == address(0)) ok += 4;
            if (afterDelete.b == 0) ok += 8;
            if (afterDelete.c == false) ok += 16;
            if (afterDelete.d == bytes32(0)) ok += 32;

            // Missing keys should also read as defaults.
            if (mBytes[k2] == bytes32(0)) ok += 64;
            if (mAddr[k2] == address(0)) ok += 128;
            S memory missing = mStruct[k2];
            if (missing.a == address(0)) ok += 256;
            if (missing.b == 0) ok += 512;
            if (missing.c == false) ok += 1024;
            if (missing.d == bytes32(0)) ok += 2048;

            return ok;
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    let result = execute_bytecode(&artifacts[0].bytecode);
    let failure = result
        .exception
        .as_ref()
        .map(|ex| ex.message.as_str())
        .unwrap_or("<no exception>");
    assert!(
        result.is_success(),
        "expected delete harness to succeed, got: {failure}"
    );
    assert_eq!(result.return_data, 4095i64.to_le_bytes().to_vec());
}

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

    contract PopHarness {
        uint256[] public values;

        function popOnce() public returns (uint256) {
            return values.pop();
        }
    }
    "#;

    let metadata = analyse_source(source).expect("analysis failed");
    let module = ir::Module::from_contract(&metadata).expect("IR lowering failed");
    let pop_fn = module
        .functions
        .iter()
        .find(|function| function.name == "popOnce")
        .expect("expected popOnce");

    let instrs = &pop_fn.basic_blocks[0].instructions;
    let has_guard = instrs.windows(2).any(|window| {
        matches!(
            window,
            [
                ir::Instruction::BinaryOp(ir::BinaryOperator::Ne),
                ir::Instruction::JumpIf { .. }
            ]
        )
    });

    assert!(has_guard, "expected pop lowering to use Ne + JumpIf guard");
    assert!(
        instrs
            .iter()
            .any(|instr| matches!(instr, ir::Instruction::Throw)),
        "expected pop lowering to include Throw on empty array"
    );
}

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

    interface ITypes {
        struct Pair {
            uint256 a;
            bool b;
        }
    }

    contract NewAlloc is ITypes {
        function run() public pure returns (uint256) {
            bytes memory b = new bytes(4);
            if (b.length != 4) return 0;
            if (uint8(b[2]) != 0) return 0;

            string memory s = new string(3);
            if (bytes(s).length != 3) return 0;

            uint256[] memory arr = new uint256[](3);
            if (arr.length != 3) return 0;
            if (arr[1] != 0) return 0;

            Pair[] memory pairs = new Pair[](1);
            if (pairs.length != 1) return 0;
            if (pairs[0].a != 0) return 0;
            if (pairs[0].b) return 0;

            return 1;
        }
    }
    "#;

    let artifacts = compile_contracts(source, false, 2).expect("compilation failed");
    assert_eq!(artifacts.len(), 1);
    let result = execute_bytecode(&artifacts[0].bytecode);
    let failure = result
        .exception
        .as_ref()
        .map(|ex| ex.message.as_str())
        .unwrap_or("<no exception>");
    assert!(
        result.is_success(),
        "expected run() to succeed, got: {failure}"
    );
    assert_eq!(result.return_data, 1i64.to_le_bytes().to_vec());
}