// SPDX-License-Identifier: Apache-2.0 use solang::codegen::{codegen, OptimizationLevel, Options}; use solang::file_resolver::FileResolver; use solang::sema::ast::Diagnostic; use solang::sema::ast::Namespace; use solang::{parse_and_resolve, Target}; use std::ffi::OsStr; fn parse_and_codegen(src: &'static str) -> Namespace { let mut cache = FileResolver::new(); cache.set_file_contents("test.sol", src.to_string()); let mut ns = parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM); let opt = Options { dead_storage: false, constant_folding: false, strength_reduce: false, vector_to_slice: false, common_subexpression_elimination: false, opt_level: OptimizationLevel::Default, generate_debug_information: false, log_api_return_codes: false, log_runtime_errors: false, log_prints: true, }; codegen(&mut ns, &opt); ns } fn contains_error_message_and_notes( errors: &[&Diagnostic], message: &str, notes_no: usize, ) -> bool { for error in errors { if error.message == message { return error.notes.len() == notes_no; } } false } #[test] fn used_before_being_defined() { let file = r#" contract Test { bytes byteArr; bytes32 baRR; function get() public { string memory s = "Test"; byteArr = bytes(s); uint16 a = 1; uint8 b; b = uint8(a); uint256 c; c = b; bytes32 b32; bytes memory char = bytes(bytes32(uint(a) * 2 ** (8 * b))); baRR = bytes32(c); bytes32 cdr = bytes32(char); assert(b32 == baRR); if(b32 != cdr) { } } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 'b32' is undefined"); assert_eq!(errors[0].notes.len(), 2); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); assert_eq!( errors[0].notes[1].message, "Variable read before being defined" ); } #[test] fn struct_as_ref() { let file = r#" contract test_struct_parsing { struct foo { bool x; uint32 y; } function func(foo f) private { // assigning to f members dereferences f f.x = true; f.y = 64; // assigning to f changes the reference f = foo({ x: false, y: 256 }); // f no longer point to f in caller function f.x = false; f.y = 98123; } function test() public { foo f; func(f); assert(f.x == true); assert(f.y == 64); } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 'f' is undefined"); assert_eq!(errors[0].notes.len(), 3); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); assert_eq!( errors[0].notes[1].message, "Variable read before being defined" ); assert_eq!( errors[0].notes[2].message, "Variable read before being defined" ); let file = r#" contract test_struct_parsing { struct foo { bool x; uint32 y; } function func(foo f) private { // assigning to f members dereferences f f.x = true; f.y = 64; // assigning to f changes the reference f = foo({ x: false, y: 256 }); // f no longer point to f in caller function f.x = false; f.y = 98123; } function test() public { foo f = foo(false, 2); func(f); assert(f.x == true); assert(f.y == 64); } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 0); } #[test] fn while_loop() { let file = r#" contract testing { function test(int x) public pure returns (string) { string s; while(x > 0){ s = "testing_string"; x--; } return s; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 's' is undefined"); assert_eq!(errors[0].notes.len(), 1); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); let file = r#" contract testing { function test(int x) public pure returns (string) { string s; while(x > 0){ s = "testing_string"; x--; } if(x < 0) { s = "another_test"; } return s; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 's' is undefined"); assert_eq!(errors[0].notes.len(), 1); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); let file = r#" contract testing { function test(int x) public pure returns (string) { string s; while(x > 0){ s = "testing_string"; x--; } if(x < 0) { s = "another_test"; } else { s = "should_work"; } return s; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 0); } #[test] fn for_loop() { let file = r#" contract testing { function test(int x) public pure returns (int) { int s; for(int i=0; i 0); return o.a; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 0); } #[test] fn if_else_condition() { let file = r#" contract testing { struct other { int a; } function test(int x) public pure returns (int) { other o; if(x > 0) { o = other(2); } return o.a; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 'o' is undefined"); assert_eq!(errors[0].notes.len(), 1); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); let file = r#" contract testing { struct other { int a; } function test(int x) public pure returns (int) { other o; if(x > 0) { x += 2; } else if(x < 0) { o = other(2); } else { x++; } return o.a; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 'o' is undefined"); assert_eq!(errors[0].notes.len(), 1); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); let file = r#" contract testing { struct other { int a; } function test(int x) public pure returns (int) { other o; if(x > 0) { o = other(2); } else { o = other(2); } return o.a; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 0); } #[test] fn array() { let file = r#" contract test { function testing(int x) public pure returns (int) { int[] vec; return vec[0]; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 0); let file = r#" contract test { function testing(int x) public pure returns (int) { int[] vec; if(x > 0) { vec.push(2); } return vec[0]; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 0); } #[test] fn contract_and_enum() { let file = r#" contract other { int public a; function testing() public returns (int) { return 2; } } contract test { enum FreshJuiceSize{ SMALL, MEDIUM, LARGE } function testing(int x) public returns (int) { other o; FreshJuiceSize choice; if(x > 0 && o.testing() < 5) { o = new other(); } assert(choice == FreshJuiceSize.LARGE); return o.a(); } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert!(contains_error_message_and_notes( &errors, "Variable 'o' is undefined", 2 )); assert!(contains_error_message_and_notes( &errors, "Variable 'choice' is undefined", 1 )); } #[test] fn basic_types() { let file = r#" contract test { function testing(int x) public returns (address, int, uint, bool, bytes, int) { address a; int i; uint u; bool b; bytes bt; int[5] vec; while(x > 0) { x--; a = address(this); i = -2; u = 2; b = true; bt = hex"1234"; vec[0] = 2; } return (a, i, u, b, bt, vec[1]); } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert!(contains_error_message_and_notes( &errors, "Variable 'bt' is undefined", 1 )); assert!(contains_error_message_and_notes( &errors, "Variable 'b' is undefined", 1 )); assert!(contains_error_message_and_notes( &errors, "Variable 'a' is undefined", 1 )); assert!(contains_error_message_and_notes( &errors, "Variable 'i' is undefined", 1 )); assert!(contains_error_message_and_notes( &errors, "Variable 'u' is undefined", 1 )); } #[test] fn nested_branches() { let file = r#" contract test { function testing(int x) public returns (int) { int i; while(x > 0) { int b; if(x > 5) { b = 2; } i = b; } int a; if(x < 5) { if(x < 2) { a = 2; } else { a = 1; } } else if(x < 4) { a = 5; } return i + a; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert!(contains_error_message_and_notes( &errors, "Variable 'b' is undefined", 1 )); assert!(contains_error_message_and_notes( &errors, "Variable 'a' is undefined", 1 )); assert!(contains_error_message_and_notes( &errors, "Variable 'i' is undefined", 2 )); } #[test] fn try_catch() { //TODO: Fix this test case // let file = r#" // contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} } // contract Example { // AddNumbers addContract; // event StringFailure(string stringFailure); // event BytesFailure(bytes bytesFailure); // // function exampleFunction(uint256 _a, uint256 _b) public returns (bytes c) { // bytes r; // try addContract.add(_a, _b) returns (uint256 _value) { // r = hex"ABCD"; // return r; // } catch Error(string memory _err) { // r = hex"ABCD"; // emit StringFailure(_err); // } catch (bytes memory _err) { // emit BytesFailure(_err); // } // // return r; // } // // } // "#; // // let ns = parse_and_codegen(file); // let errors = ns.diagnostics.errors(); // assert_eq!(errors.len(), 1); // assert_eq!(errors[0].message, "Variable 'r' is undefined"); // assert_eq!(errors[0].notes.len(), 1); // assert_eq!(errors[0].notes[0].message, "Variable read before being defined"); let file = r#" contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} } contract Example { AddNumbers addContract; event StringFailure(string stringFailure); event BytesFailure(bytes bytesFailure); function exampleFunction(uint256 _a, uint256 _b) public returns (bytes c) { bytes r; try addContract.add(_a, _b) returns (uint256 _value) { r = hex"ABCD"; return r; } catch Error(string memory _err) { r = hex"ABCD"; emit StringFailure(_err); } catch (bytes memory _err) { r = hex"ABCD"; emit BytesFailure(_err); } return r; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 0); let file = r#" contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} } contract Example { AddNumbers addContract; event StringFailure(string stringFailure); event BytesFailure(bytes bytesFailure); function exampleFunction(uint256 _a, uint256 _b) public returns (bytes c) { bytes r; try addContract.add(_a, _b) returns (uint256 _value) { return r; } catch Error(string memory _err) { r = hex"ABCD"; emit StringFailure(_err); } catch (bytes memory _err) { emit BytesFailure(_err); } return r; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 'r' is undefined"); assert_eq!(errors[0].notes.len(), 1); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); let file = r#" contract AddNumbers { function add(uint256 a, uint256 b) external pure returns (uint256 c) {c = b;} } contract Example { AddNumbers addContract; event StringFailure(string stringFailure); event BytesFailure(bytes bytesFailure); function exampleFunction(uint256 _a, uint256 _b) public returns (bytes c) { bytes r; try addContract.add(_a, _b) returns (uint256 _value) { r = hex"ABCD"; return r; } catch Error(string memory _err) { emit StringFailure(_err); } catch (bytes memory _err) { emit BytesFailure(_err); } return r; } } "#; let ns = parse_and_codegen(file); let errors = ns.diagnostics.errors(); assert_eq!(errors.len(), 1); assert_eq!(errors[0].message, "Variable 'r' is undefined"); assert_eq!(errors[0].notes.len(), 1); assert_eq!( errors[0].notes[0].message, "Variable read before being defined" ); }