mirror of
				https://github.com/ethereum/solidity
				synced 2023-10-03 13:03:40 +00:00 
			
		
		
		
	Merge pull request #4222 from ethereum/view-pure-checker-split
Extract test cases from ViewPureChecker
This commit is contained in:
		
						commit
						d97aca77f2
					
				| @ -38,71 +38,6 @@ namespace test | ||||
| 
 | ||||
| BOOST_FIXTURE_TEST_SUITE(ViewPureChecker, AnalysisFramework) | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(smoke_test) | ||||
| { | ||||
| 	char const* text = R"( | ||||
| 		contract C { | ||||
| 			uint x; | ||||
| 			function g() pure public {} | ||||
| 			function f() view public returns (uint) { return now; } | ||||
| 			function h() public { x = 2; } | ||||
| 			function i() payable public { x = 2; } | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(call_internal_functions_success) | ||||
| { | ||||
| 	char const* text = R"( | ||||
| 		contract C { | ||||
| 			function g() pure public { g(); } | ||||
| 			function f() view public returns (uint) { f(); g(); } | ||||
| 			function h() public { h(); g(); f(); } | ||||
| 			function i() payable public { i(); h(); g(); f(); } | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(suggest_pure) | ||||
| { | ||||
| 	char const* text = R"( | ||||
| 		contract C { | ||||
| 			function g() view public { } | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_WARNING(text, "can be restricted to pure"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(suggest_view) | ||||
| { | ||||
| 	char const* text = R"( | ||||
| 		contract C { | ||||
| 			uint x; | ||||
| 			function g() public returns (uint) { return x; } | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_WARNING(text, "can be restricted to view"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(call_internal_functions_fail) | ||||
| { | ||||
| 	CHECK_ERROR( | ||||
| 		"contract C{ function f() pure public { g(); } function g() view public {} }", | ||||
| 		TypeError, | ||||
| 		"Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires \"view\"" | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(write_storage_fail) | ||||
| { | ||||
| 	CHECK_WARNING( | ||||
| 		"contract C{ uint x; function f() view public { x = 2; } }", | ||||
| 		"Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable." | ||||
| 	); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(environment_access) | ||||
| { | ||||
| 	vector<string> view{ | ||||
| @ -163,275 +98,6 @@ BOOST_AUTO_TEST_CASE(environment_access) | ||||
| 	})); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(view_error_for_050) | ||||
| { | ||||
| 	CHECK_ERROR( | ||||
| 		"pragma experimental \"v0.5.0\"; contract C { uint x; function f() view public { x = 2; } }", | ||||
| 		TypeError, | ||||
| 		"Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable." | ||||
| 	); | ||||
| 
 | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(modifiers) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract D { | ||||
| 			uint x; | ||||
| 			modifier purem(uint) { _; } | ||||
| 			modifier viewm(uint) { uint a = x; _; a; } | ||||
| 			modifier nonpayablem(uint) { x = 2; _; } | ||||
| 		} | ||||
| 		contract C is D { | ||||
| 			function f() purem(0) pure public {} | ||||
| 			function g() viewm(0) view public {} | ||||
| 			function h() nonpayablem(0) public {} | ||||
| 			function i() purem(x) view public {} | ||||
| 			function j() viewm(x) view public {} | ||||
| 			function k() nonpayablem(x) public {} | ||||
| 			function l() purem(x = 2) public {} | ||||
| 			function m() viewm(x = 2) public {} | ||||
| 			function n() nonpayablem(x = 2) public {} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(interface) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		interface D { | ||||
| 			function f() view external; | ||||
| 		} | ||||
| 		contract C is D { | ||||
| 			function f() view external {} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(overriding) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract D { | ||||
| 			uint x; | ||||
| 			function f() public { x = 2; } | ||||
| 		} | ||||
| 		contract C is D { | ||||
| 			function f() public {} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(returning_structs) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			struct S { uint x; } | ||||
| 			S s; | ||||
| 			function f() view internal returns (S storage) { | ||||
| 				return s; | ||||
| 			} | ||||
| 			function g() public { | ||||
| 				f().x = 2; | ||||
| 			} | ||||
| 			function h() view public { | ||||
| 				f(); | ||||
| 				f().x; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(mappings) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			mapping(uint => uint) a; | ||||
| 			function f() view public { | ||||
| 				a; | ||||
| 			} | ||||
| 			function g() view public { | ||||
| 				a[2]; | ||||
| 			} | ||||
| 			function h() public { | ||||
| 				a[2] = 3; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(local_storage_variables) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			struct S { uint a; } | ||||
| 			S s; | ||||
| 			function f() view public { | ||||
| 				S storage x = s; | ||||
| 				x; | ||||
| 			} | ||||
| 			function g() view public { | ||||
| 				S storage x = s; | ||||
| 				x = s; | ||||
| 			} | ||||
| 			function i() public { | ||||
| 				s.a = 2; | ||||
| 			} | ||||
| 			function h() public { | ||||
| 				S storage x = s; | ||||
| 				x.a = 2; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(builtin_functions) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			function f() public { | ||||
| 				address(this).transfer(1); | ||||
| 				require(address(this).send(2)); | ||||
| 				selfdestruct(address(this)); | ||||
| 				require(address(this).delegatecall()); | ||||
| 				require(address(this).call()); | ||||
| 			} | ||||
| 			function g() pure public { | ||||
| 				bytes32 x = keccak256("abc"); | ||||
| 				bytes32 y = sha256("abc"); | ||||
| 				address z = ecrecover(1, 2, 3, 4); | ||||
| 				require(true); | ||||
| 				assert(true); | ||||
| 				x; y; z; | ||||
| 			} | ||||
| 			function() payable public {} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(function_types) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			function f() pure public { | ||||
| 				function () external nonpayFun; | ||||
| 				function () external view viewFun; | ||||
| 				function () external pure pureFun; | ||||
| 
 | ||||
| 				nonpayFun; | ||||
| 				viewFun; | ||||
| 				pureFun; | ||||
| 				pureFun(); | ||||
| 			} | ||||
| 			function g() view public { | ||||
| 				function () external view viewFun; | ||||
| 
 | ||||
| 				viewFun(); | ||||
| 			} | ||||
| 			function h() public { | ||||
| 				function () external nonpayFun; | ||||
| 
 | ||||
| 				nonpayFun(); | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(selector) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			uint public x; | ||||
| 			function f() payable public { | ||||
| 			} | ||||
| 			function g() pure public returns (bytes4) { | ||||
| 				return this.f.selector ^ this.x.selector; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(selector_complex) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			function f(C c) pure public returns (C) { | ||||
| 				return c; | ||||
| 			} | ||||
| 			function g() pure public returns (bytes4) { | ||||
| 				// By passing `this`, we read from the state, even if f itself is pure.
 | ||||
| 				return f(this).f.selector; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_ERROR(text, TypeError, "reads from the environment or state and thus requires \"view\""); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(selector_complex2) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 				function f() payable public returns (C) { | ||||
| 				return this; | ||||
| 			} | ||||
| 			function g() pure public returns (bytes4) { | ||||
| 				C x = C(0x123); | ||||
| 				return x.f.selector; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(creation) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract D {} | ||||
| 		contract C { | ||||
| 			function f() public { new D(); } | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(assembly) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			struct S { uint x; } | ||||
| 			S s; | ||||
| 			function e() pure public { | ||||
| 				assembly { mstore(keccak256(0, 20), mul(s_slot, 2)) } | ||||
| 			} | ||||
| 			function f() pure public { | ||||
| 				uint x; | ||||
| 				assembly { x := 7 } | ||||
| 			} | ||||
| 			function g() view public { | ||||
| 				assembly { for {} 1 { pop(sload(0)) } { } pop(gas) } | ||||
| 			} | ||||
| 			function h() view public { | ||||
| 				assembly { function g() { pop(blockhash(20)) } } | ||||
| 			} | ||||
| 			function j() public { | ||||
| 				assembly { pop(call(0, 1, 2, 3, 4, 5, 6)) } | ||||
| 			} | ||||
| 			function k() public { | ||||
| 				assembly { pop(call(gas, 1, 2, 3, 4, 5, 6)) } | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(assembly_staticcall) | ||||
| { | ||||
| 	string text = R"( | ||||
| @ -447,31 +113,6 @@ BOOST_AUTO_TEST_CASE(assembly_staticcall) | ||||
| 		CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(assembly_jump) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			function k() public { | ||||
| 				assembly { jump(2) } | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_WARNING(text, "low-level EVM features"); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_CASE(constant) | ||||
| { | ||||
| 	string text = R"( | ||||
| 		contract C { | ||||
| 			uint constant x = 2; | ||||
| 			function k() pure public returns (uint) { | ||||
| 				return x; | ||||
| 			} | ||||
| 		} | ||||
| 	)"; | ||||
| 	CHECK_SUCCESS_NO_WARNINGS(text); | ||||
| } | ||||
| 
 | ||||
| BOOST_AUTO_TEST_SUITE_END() | ||||
| 
 | ||||
| } | ||||
|  | ||||
							
								
								
									
										23
									
								
								test/libsolidity/syntaxTests/viewPureChecker/assembly.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										23
									
								
								test/libsolidity/syntaxTests/viewPureChecker/assembly.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,23 @@ | ||||
| contract C { | ||||
|     struct S { uint x; } | ||||
|     S s; | ||||
|     function e() pure public { | ||||
|         assembly { mstore(keccak256(0, 20), mul(s_slot, 2)) } | ||||
|     } | ||||
|     function f() pure public { | ||||
|         uint x; | ||||
|         assembly { x := 7 } | ||||
|     } | ||||
|     function g() view public { | ||||
|         assembly { for {} 1 { pop(sload(0)) } { } pop(gas) } | ||||
|     } | ||||
|     function h() view public { | ||||
|         assembly { function g() { pop(blockhash(20)) } } | ||||
|     } | ||||
|     function j() public { | ||||
|         assembly { pop(call(0, 1, 2, 3, 4, 5, 6)) } | ||||
|     } | ||||
|     function k() public { | ||||
|         assembly { pop(call(gas, 1, 2, 3, 4, 5, 6)) } | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| contract C { | ||||
|     function k() public { | ||||
|         assembly { jump(2) } | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // Warning: (58-65): Jump instructions and labels are low-level EVM features that can lead to incorrect stack access. Because of that they are discouraged. Please consider using "switch", "if" or "for" statements instead. | ||||
| @ -0,0 +1,18 @@ | ||||
| contract C { | ||||
|     function f() public { | ||||
|         address(this).transfer(1); | ||||
|         require(address(this).send(2)); | ||||
|         selfdestruct(address(this)); | ||||
|         require(address(this).delegatecall()); | ||||
|         require(address(this).call()); | ||||
|     } | ||||
|     function g() pure public { | ||||
|         bytes32 x = keccak256("abc"); | ||||
|         bytes32 y = sha256("abc"); | ||||
|         address z = ecrecover(1, 2, 3, 4); | ||||
|         require(true); | ||||
|         assert(true); | ||||
|         x; y; z; | ||||
|     } | ||||
|     function() payable public {} | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| contract C { | ||||
|     function f() pure public { g(); } | ||||
|     function g() view public {} | ||||
| } | ||||
| // ---- | ||||
| // TypeError: (44-47): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". | ||||
| // Warning: (55-82): Function state mutability can be restricted to pure | ||||
| @ -0,0 +1,6 @@ | ||||
| contract C { | ||||
|     function g() pure public { g(); } | ||||
|     function f() view public returns (uint) { f(); g(); } | ||||
|     function h() public { h(); g(); f(); } | ||||
|     function i() payable public { i(); h(); g(); f(); } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| contract C { | ||||
|     uint constant x = 2; | ||||
|     function k() pure public returns (uint) { | ||||
|         return x; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,4 @@ | ||||
| contract D {} | ||||
| contract C { | ||||
|     function f() public { new D(); } | ||||
| } | ||||
| @ -0,0 +1,22 @@ | ||||
| contract C { | ||||
|     function f() pure public { | ||||
|         function () external nonpayFun; | ||||
|         function () external view viewFun; | ||||
|         function () external pure pureFun; | ||||
| 
 | ||||
|         nonpayFun; | ||||
|         viewFun; | ||||
|         pureFun; | ||||
|         pureFun(); | ||||
|     } | ||||
|     function g() view public { | ||||
|         function () external view viewFun; | ||||
| 
 | ||||
|         viewFun(); | ||||
|     } | ||||
|     function h() public { | ||||
|         function () external nonpayFun; | ||||
| 
 | ||||
|         nonpayFun(); | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,6 @@ | ||||
| interface D { | ||||
|     function f() view external; | ||||
| } | ||||
| contract C is D { | ||||
|     function f() view external {} | ||||
| } | ||||
| @ -0,0 +1,19 @@ | ||||
| contract C { | ||||
|     struct S { uint a; } | ||||
|     S s; | ||||
|     function f() view public { | ||||
|         S storage x = s; | ||||
|         x; | ||||
|     } | ||||
|     function g() view public { | ||||
|         S storage x = s; | ||||
|         x = s; | ||||
|     } | ||||
|     function i() public { | ||||
|         s.a = 2; | ||||
|     } | ||||
|     function h() public { | ||||
|         S storage x = s; | ||||
|         x.a = 2; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										12
									
								
								test/libsolidity/syntaxTests/viewPureChecker/mappings.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										12
									
								
								test/libsolidity/syntaxTests/viewPureChecker/mappings.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,12 @@ | ||||
| contract C { | ||||
|     mapping(uint => uint) a; | ||||
|     function f() view public { | ||||
|         a; | ||||
|     } | ||||
|     function g() view public { | ||||
|         a[2]; | ||||
|     } | ||||
|     function h() public { | ||||
|         a[2] = 3; | ||||
|     } | ||||
| } | ||||
							
								
								
									
										17
									
								
								test/libsolidity/syntaxTests/viewPureChecker/modifiers.sol
									
									
									
									
									
										Normal file
									
								
							
							
						
						
									
										17
									
								
								test/libsolidity/syntaxTests/viewPureChecker/modifiers.sol
									
									
									
									
									
										Normal file
									
								
							| @ -0,0 +1,17 @@ | ||||
| contract D { | ||||
|     uint x; | ||||
|     modifier purem(uint) { _; } | ||||
|     modifier viewm(uint) { uint a = x; _; a; } | ||||
|     modifier nonpayablem(uint) { x = 2; _; } | ||||
| } | ||||
| contract C is D { | ||||
|     function f() purem(0) pure public {} | ||||
|     function g() viewm(0) view public {} | ||||
|     function h() nonpayablem(0) public {} | ||||
|     function i() purem(x) view public {} | ||||
|     function j() viewm(x) view public {} | ||||
|     function k() nonpayablem(x) public {} | ||||
|     function l() purem(x = 2) public {} | ||||
|     function m() viewm(x = 2) public {} | ||||
|     function n() nonpayablem(x = 2) public {} | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| contract D { | ||||
|     uint x; | ||||
|     function f() public { x = 2; } | ||||
| } | ||||
| contract C is D { | ||||
|     function f() public {} | ||||
| } | ||||
| @ -0,0 +1,14 @@ | ||||
| contract C { | ||||
|     struct S { uint x; } | ||||
|     S s; | ||||
|     function f() view internal returns (S storage) { | ||||
|         return s; | ||||
|     } | ||||
|     function g() public { | ||||
|         f().x = 2; | ||||
|     } | ||||
|     function h() view public { | ||||
|         f(); | ||||
|         f().x; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,8 @@ | ||||
| contract C { | ||||
|     uint public x; | ||||
|     function f() payable public { | ||||
|     } | ||||
|     function g() pure public returns (bytes4) { | ||||
|         return this.f.selector ^ this.x.selector; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,11 @@ | ||||
| contract C { | ||||
|     function f(C c) pure public returns (C) { | ||||
|         return c; | ||||
|     } | ||||
|     function g() pure public returns (bytes4) { | ||||
|         // By passing `this`, we read from the state, even if f itself is pure. | ||||
|         return f(this).f.selector; | ||||
|     } | ||||
| } | ||||
| // ---- | ||||
| // TypeError: (228-232): Function declared as pure, but this expression (potentially) reads from the environment or state and thus requires "view". | ||||
| @ -0,0 +1,9 @@ | ||||
| contract C { | ||||
|         function f() payable public returns (C) { | ||||
|         return this; | ||||
|     } | ||||
|     function g() pure public returns (bytes4) { | ||||
|         C x = C(0x123); | ||||
|         return x.f.selector; | ||||
|     } | ||||
| } | ||||
| @ -0,0 +1,7 @@ | ||||
| contract C { | ||||
|     uint x; | ||||
|     function g() pure public {} | ||||
|     function f() view public returns (uint) { return now; } | ||||
|     function h() public { x = 2; } | ||||
|     function i() payable public { x = 2; } | ||||
| } | ||||
| @ -0,0 +1,5 @@ | ||||
| contract C { | ||||
|     function g() view public { } | ||||
| } | ||||
| // ---- | ||||
| // Warning: (17-45): Function state mutability can be restricted to pure | ||||
| @ -0,0 +1,6 @@ | ||||
| contract C { | ||||
|     uint x; | ||||
|     function g() public returns (uint) { return x; } | ||||
| } | ||||
| // ---- | ||||
| // Warning: (29-77): Function state mutability can be restricted to view | ||||
| @ -0,0 +1,6 @@ | ||||
| contract C { | ||||
|     uint x; | ||||
|     function f() view public { x = 2; } | ||||
| } | ||||
| // ---- | ||||
| // Warning: (56-57): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. | ||||
| @ -0,0 +1,7 @@ | ||||
| pragma experimental "v0.5.0"; | ||||
| contract C { | ||||
|     uint x; | ||||
|     function f() view public { x = 2; } | ||||
| } | ||||
| // ---- | ||||
| // TypeError: (86-87): Function declared as view, but this expression (potentially) modifies the state and thus requires non-payable (the default) or payable. | ||||
		Loading…
	
		Reference in New Issue
	
	Block a user