use solang::file_resolver::FileResolver;
use solang::sema::ast;
use solang::{parse_and_resolve, Target};
use std::ffi::OsStr;
fn parse(src: &'static str) -> ast::Namespace {
let mut cache = FileResolver::new();
cache.set_file_contents("test.sol", src.to_string());
parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM)
}
fn parse_two_files(src1: &'static str, src2: &'static str) -> ast::Namespace {
let mut cache = FileResolver::new();
cache.set_file_contents("test.sol", src1.to_string());
cache.set_file_contents("test2.sol", src2.to_string());
parse_and_resolve(OsStr::new("test.sol"), &mut cache, Target::EVM)
}
#[test]
fn emit_event() {
let case_1 = r#"
contract usedEvent {
event Hey(uint8 n);
function emitEvent(uint8 n) public {
emit Hey(n);
}
}
"#;
let ns = parse(case_1);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let case_2 = r#"
event Hey(uint8 n);
contract usedEvent {
event Hey(uint8 n);
event Hello(uint8 n);
function emitEvent(uint8 n) public {
emit Hey(n);
}
}
"#;
let ns = parse(case_2);
assert_eq!(ns.diagnostics.count_warnings(), 1);
assert_eq!(
ns.diagnostics.first_warning().message,
"event 'Hello' has never been emitted"
);
let case_2 = r#"
contract F {
event Hey(uint8 n);
event Hello(uint8 n);
}
contract usedEvent is F {
event Hey(uint8 n);
function emitEvent(uint8 n) public {
emit Hey(n);
}
}
"#;
let ns = parse(case_2);
assert_eq!(ns.diagnostics.count_warnings(), 1);
assert_eq!(
ns.diagnostics.first_warning().message,
"event 'Hello' has never been emitted"
);
let case_2 = r#"
contract F {
event Hey(uint8 n);
}
contract usedEvent is F {
event Hey(uint8 n);
function emitEvent(uint8 n) public {
// reference event in contract F, so our event decl is not used
emit F.Hey(n);
}
}
"#;
let ns = parse(case_2);
assert_eq!(ns.diagnostics.count_warnings(), 1);
assert_eq!(
ns.diagnostics.first_warning().message,
"event 'Hey' has never been emitted"
);
let case_3 = r#"
abstract contract F {
event Hey(uint8 n);
}
interface G {
event Hey(uint8 n);
}
"#;
let ns = parse(case_3);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn constant_variable() {
let file_2 = r#"
uint32 constant outside = 2;
"#;
let file_1 = r#"
import "test2.sol";
contract Testing {
uint32 test;
uint32 constant cte = 5;
constructor() {
test = outside;
test = cte;
}
function get() public view returns (uint32) {
return test;
}
}
"#;
let ns = parse_two_files(file_1, file_2);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let file_1 = r#"
import "test2.sol";
contract Testing {
uint32 test;
uint32 constant cte = 5;
constructor() {
test = 45;
}
function get() public view returns (uint32) {
return test;
}
}
"#;
let ns = parse_two_files(file_1, file_2);
assert_eq!(ns.diagnostics.count_warnings(), 2);
assert!(ns
.diagnostics
.warning_contains("storage variable 'cte' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("global constant 'outside' has never been used"));
}
#[test]
fn storage_variable() {
let file = r#"
contract Test {
string str = "This is a test";
string str2;
constructor() {
str = "This is another test";
}
}
"#;
let ns = parse(file);
let warnings = ns.diagnostics.warnings();
assert_eq!(warnings.len(), 2);
assert_eq!(
warnings[0].message,
"storage variable 'str' has been assigned, but never read"
);
assert_eq!(
warnings[1].message,
"storage variable 'str2' has never been used"
);
let file = r#"
contract Test {
string str = "This is a test";
string str2;
constructor() {
str = "This is another test";
str2 = str;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 1);
assert_eq!(
ns.diagnostics.first_warning().message,
"storage variable 'str2' has been assigned, but never read"
);
let file = r#"
contract Test {
string str = "This is a test";
constructor() {
str = "This is another test";
}
}
contract Test2 is Test {
function get() public view returns (string) {
return str;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn state_variable() {
let file = r#"
contract Test {
function get() public pure {
uint32 a = 1;
uint32 b;
b = 1;
uint32 c;
uint32 d;
d = 1;
uint32 e;
e = d*5;
d = e/5;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 3);
assert!(ns
.diagnostics
.warning_contains("local variable 'b' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("local variable 'a' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("local variable 'c' has never been read nor assigned"));
}
#[test]
fn struct_usage() {
let file = r#"
struct testing {
uint8 it;
bool tf;
}
contract Test {
testing t1;
testing t4;
testing t6;
constructor() {
t1 = testing(8, false);
}
function modify() public returns (uint8) {
testing memory t2;
t2.it = 4;
t4 = testing(1, true);
testing storage t3 = t4;
uint8 k = 2*4/t3.it;
testing t5;
return k;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 4);
assert!(ns
.diagnostics
.warning_contains("local variable 't2' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("storage variable 't1' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("local variable 't5' has never been read nor assigned"));
assert!(ns
.diagnostics
.warning_contains("storage variable 't6' has never been used"));
}
#[test]
fn subscript() {
let file = r#"
contract Test {
int[] arr1;
int[4] arr2;
int[4] arr3;
bytes byteArr;
uint constant e = 1;
function get() public {
uint8 a = 1;
uint8 b = 2;
arr1[a] = 1;
arr2[a+b] = 2;
uint8 c = 1;
uint8 d = 1;
int[] memory arr4;
arr4[0] = 1;
int[4] storage arr5 = arr3;
arr5[c*d] = 1;
byteArr[e] = 0x05;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 4);
assert!(ns
.diagnostics
.warning_contains("local variable 'arr4' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("storage variable 'arr1' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("storage variable 'arr2' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("storage variable 'byteArr' has been assigned, but never read"));
let file = r#"
contract Test {
int[] arr1;
int[4] arr2;
int[4] arr3;
bytes byteArr;
uint constant e = 1;
function get() public {
uint8 a = 1;
uint8 b = 2;
arr1[a] = 1;
arr2[a+b] = 2;
assert(arr1[a] == arr2[b]);
uint8 c = 1;
uint8 d = 1;
int[] memory arr4;
arr4[0] = 1;
int[4] storage arr5 = arr3;
arr5[c*d] = 1;
assert(arr4[c] == arr5[d]);
assert(arr3[c] == arr5[d]);
byteArr[e] = 0x05;
assert(byteArr[e] == byteArr[e]);
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn assign_trunc_cast() {
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(file);
assert_eq!(ns.diagnostics.count_warnings(), 1);
assert!(ns
.diagnostics
.warning_contains("storage variable 'byteArr' has been assigned, but never read"));
}
#[test]
fn array_length() {
let file = r#"
contract Test {
int[5] arr1;
int[] arr2;
function get() public view returns (bool) {
int[5] memory arr3;
int[] memory arr4;
bool test = false;
if(arr1.length == arr2.length) {
test = true;
}
else if(arr3.length != arr4.length) {
test = false;
}
return test;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn sign_ext_storage_load() {
let file = r#"
contract Test {
bytes a;
function use(bytes memory b) pure public {
assert(b[0] == b[1]);
}
function get() public pure returns (int16 ret) {
use(a);
int8 b = 1;
int16 c = 1;
int16 d;
d = c << b;
ret = d;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn statements() {
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 (uint256 _c) {
try addContract.add(_a, _b) returns (uint256 _value) {
return (_value);
} catch Error(string memory _err) {
// This may occur if there is an overflow with the two numbers and the `AddNumbers` contract explicitly fails with a `revert()`
emit StringFailure(_err);
} catch (bytes memory _err) {
emit BytesFailure(_err);
}
}
function testFunction() pure public {
int three = 3;
{
int test = 2;
int c = test*3;
while(c != test) {
c -= three;
}
}
int four = 4;
int test = 3;
do {
int ct = 2;
} while(four > test);
}
function bytesToUInt(uint v) public pure returns (uint ret) {
if (v == 0) {
ret = 0;
}
else {
while (v > 0) {
ret = uint(uint(ret) / (2 ** 8));
ret |= uint(((v % 10) + 48) * 2 ** (8 * 31));
v /= 10;
}
}
return ret;
}
function stringToUint(string s) public pure returns (uint result) {
bytes memory b = bytes(s);
uint i;
result = 0;
for (i = 0; i < b.length; i++) {
uint c = uint(b[i]);
if (c >= 48 && c <= 57) {
result = result * 10 + (c - 48);
}
}
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 2);
assert!(ns
.diagnostics
.warning_contains("function parameter 'a' has never been read"));
assert!(ns
.diagnostics
.warning_contains("local variable 'ct' has been assigned, but never read",));
}
#[test]
fn function_call() {
let file = r#"
contract Test1 {
uint32 public a;
constructor(uint32 b) {
a = b;
}
}
contract Test2{
function test(uint32 v1, uint32 v2) private returns (uint32) {
uint32 v = 1;
Test1 t = new Test1(v);
uint32[2] memory vec = [v2, v1];
return vec[0] + t.a();
}
function callTest() public {
uint32 ta = 1;
uint32 tb = 2;
ta = test(ta, tb);
}
}
contract C {
uint public data;
function x() public returns (uint) {
data = 3;
return this.data();
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let file = r#"
contract Test1 {
uint32 public a;
constructor(uint32 b) {
a = b;
}
}
contract Test2 is Test1{
constructor(uint32 val) Test1(val) {}
function test(uint32 v1, uint32 v2) private returns (uint32) {
uint32 v = 1;
Test1 t = new Test1(v);
uint32[2] memory vec = [v2, v1];
return vec[0] + t.a();
}
function callTest() public {
uint32 ta = 1;
uint32 tb = 2;
ta = test(ta, tb);
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn array_push_pop() {
let file = r#"
contract Test1 {
uint32[] vec1;
function testVec() public {
uint32 a = 1;
uint32 b = 2;
uint32[] memory vec2;
vec1.push(a);
vec2.push(b);
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 2);
assert!(ns
.diagnostics
.warning_contains("local variable 'vec2' has been assigned, but never read"));
assert!(ns
.diagnostics
.warning_contains("storage variable 'vec1' has been assigned, but never read"));
let file = r#"
contract Test1 {
uint32[] vec1;
function testVec() public {
uint32 a = 1;
uint32 b = 2;
uint32[] memory vec2;
vec1.push(a);
vec2.push(b);
vec1.pop();
vec2.pop();
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn return_variable() {
let file = r#"
contract Test1 {
string testing;
function test1() public pure returns (uint32 ret, string memory ret2) {
return (2, "Testing is fun");
}
function test2() public returns (uint32 hey) {
(uint32 a, string memory t) = test1();
testing = t;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 3);
assert!(ns
.diagnostics
.warning_contains("destructure variable 'a' has never been used"));
assert!(ns
.diagnostics
.warning_contains("return variable 'hey' has never been assigned"));
assert!(ns
.diagnostics
.warning_contains("storage variable 'testing' has been assigned, but never read"));
}
#[test]
fn try_catch() {
let file = r#"
contract CalledContract {}
contract TryCatcher {
event SuccessEvent(bool t);
event CatchEvent(bool t);
function execute() public {
try new CalledContract() returns(CalledContract returnedInstance) {
emit SuccessEvent(true);
} catch Error(string memory revertReason) {
emit CatchEvent(true);
} catch (bytes memory returnData) {
emit CatchEvent(false);
}
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 3);
assert!(ns
.diagnostics
.warning_contains("try-catch error bytes 'returnData' has never been used"));
assert!(ns
.diagnostics
.warning_contains("try-catch returns variable 'returnedInstance' has never been read"));
assert!(ns
.diagnostics
.warning_contains("try-catch error string 'revertReason' has never been used"));
let file = r#"
contract CalledContract {
bool public ok = true;
bool private notOk = false;
}
contract TryCatcher {
event SuccessEvent(bool t);
event CatchEvent(string t);
event CatchBytes(bytes t);
function execute() public {
try new CalledContract() returns(CalledContract returnedInstance) {
// returnedInstance can be used to obtain the address of the newly deployed contract
emit SuccessEvent(returnedInstance.ok());
} catch Error(string memory revertReason) {
emit CatchEvent(revertReason);
} catch (bytes memory returnData) {
emit CatchBytes(returnData);
}
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 1);
assert!(ns
.diagnostics
.warning_contains("storage variable 'notOk' has been assigned, but never read"));
let file = r#"
contract CalledContract {
bool public ok;
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn destructure() {
let file = r#"
contract Test2{
function callTest() public view returns (uint32 ret) {
uint32 ta = 1;
uint32 tb = 2;
uint32 te = 3;
string memory tc = "hey";
bytes memory td = bytes(tc);
address nameReg = address(this);
(bool tf,) = nameReg.call(td);
ta = tf? tb : te;
uint8 tg = 1;
uint8 th = 2;
(tg, th) = (th, tg);
return ta;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn struct_initialization() {
let file = r#"
contract Test1{
struct Test2{
uint8 a;
uint8 b;
}
function callTest() public pure returns (uint32 ret) {
uint8 tg = 1;
uint8 th = 2;
Test2 memory t2;
t2 = Test2(tg, th);
ret = t2.a;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn subarray_mapping_struct_literal() {
let file = r#"
contract T {
int p;
constructor(int b) {
p = b;
}
function sum(int a, int b) virtual public returns (int){
uint8 v1 = 1;
uint8 v2 = 2;
uint8 v3 = 3;
uint8 v4 = 4;
uint8[2][2] memory v = [[v1, v2], [v3, v4]];
return a + b * p/v[0][1];
}
}
contract Test is T(2){
struct fooStruct {
int foo;
int figther;
}
mapping(string => int) public mp;
enum FreshJuiceSize{ SMALL, MEDIUM, LARGE }
FreshJuiceSize choice;
function sum(int a, int b) override public returns (int) {
choice = FreshJuiceSize.LARGE;
return a*b;
}
function test() public returns (int){
int a = 1;
int b = 2;
int c = super.sum(a, b);
int d = 3;
fooStruct memory myStruct = fooStruct({foo: c, figther: d});
string memory t = "Do some tests";
mp[t] = myStruct.figther;
return mp[t];
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 1);
assert!(ns
.diagnostics
.warning_contains("storage variable 'choice' has been assigned, but never read"));
}
#[test]
fn builtin_call_destructure() {
let file = r#"
contract Test {
function test() public returns(bool p) {
uint128 b = 1;
uint64 g = 2;
address payable ad = payable(address(this));
bytes memory by = hex"AB2";
(p, ) = ad.call{value: b, gas: g}(by);
uint c = 1;
abi.encodeWithSignature("hey", c);
uint128 amount = 2;
ad.send(amount);
uint128 amount2 = 1;
ad.transfer(amount2);
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn delete_statement() {
let file = r#"
pragma solidity 0;
contract Test1{
int test8var;
function test8() public {
delete test8var;
test8var = 2;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn load_length() {
let file = r#"
contract foo {
function f(uint i1) public pure returns (int) {
int[8] bar = [ int(10), 20, 30, 4, 5, 6, 7, 8 ];
bar[2] = 0x7_f;
return bar[i1];
}
function barfunc() public pure returns (uint) {
uint[2][3][4] array;
return array.length;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn address_selector() {
let file = r#"
contract ctc {
function foo(int32 a) public pure returns (bool) {
return a==1;
}
function test() public view {
function(int32) external returns (bool) func = this.foo;
assert(address(this) == func.address);
assert(func.selector == hex"42761137");
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn load_storage_load() {
let file = r#"
struct X {
uint32 f1;
bool f2;
}
contract foo {
function get() public pure returns (X[4] f) {
f[1].f1 = 102;
f[1].f2 = true;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn variable_function() {
let file = r#"
contract ft is Arith {
function test(bool action, int32 a, int32 b) public returns (int32) {
function(int32,int32) internal returns (int32) func;
if (action) {
func = Arith.mul;
} else {
func = Arith.add;
}
return func(a, b);
}
}
contract Arith {
function mul(int32 a, int32 b) internal pure returns (int32) {
return a * b;
}
function add(int32 a, int32 b) internal pure returns (int32) {
return a + b;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let file = r#"
contract ft {
function test() public {
function(int32) external returns (uint64) func = this.foo;
assert(func(102) == 0xabbaabba);
}
function foo(int32) public pure returns (uint64) {
return 0xabbaabba;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let file = r#"
contract ft {
function(int32,int32) internal returns (int32) func;
function mul(int32 a, int32 b) internal pure returns (int32) {
return a * b;
}
function add(int32 a, int32 b) internal pure returns (int32) {
return a + b;
}
function set_op(bool action) public {
if (action) {
func = mul;
} else {
func = add;
}
}
function test(int32 a, int32 b) public returns (int32) {
return func(a, b);
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let file = r#"
contract ft {
function mul(int32 a, int32 b) internal pure returns (int32) {
return a * b;
}
function add(int32 a, int32 b) internal pure returns (int32) {
return a + b;
}
function test(bool action, int32 a, int32 b) public returns (int32) {
function(int32,int32) internal returns (int32) func;
if (action) {
func = mul;
} else {
func = add;
}
return func(a, b);
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let file = r#"
contract ft is Arith {
function mul(int32 a, int32 b) internal pure override returns (int32) {
return a * b * 10;
}
function add(int32 a, int32 b) internal pure override returns (int32) {
return a + b + 10;
}
}
contract Arith {
function test(bool action, int32 a, int32 b) public returns (int32) {
function(int32,int32) internal returns (int32) func;
if (action) {
func = mul;
} else {
func = add;
}
return func(a, b);
}
function mul(int32 a, int32 b) internal virtual returns (int32) {
return a * b;
}
function add(int32 a, int32 b) internal virtual returns (int32) {
return a + b;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
let file = r#"
function global_function() pure returns (uint32) {
return 102;
}
function global_function2() pure returns (uint32) {
return global_function() + 5;
}
contract c {
function test() public {
function() internal returns (uint32) ftype = global_function2;
uint64 x = ftype();
assert(x == 107);
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn format_string() {
let file = r#"
contract foo {
constructor() {
int x = 21847450052839212624230656502990235142567050104912751880812823948662932355201;
print("x = {}".format(x));
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}
#[test]
fn balance() {
let file = r#"
contract foo {
function test(address payable addr) public pure returns (bool) {
bool p;
p = addr.balance == 2;
return p;
}
}
"#;
let ns = parse(file);
assert_eq!(ns.diagnostics.count_warnings(), 0);
}