diff --git a/crates/pyrometer/tests/no_killed_ctxs.rs b/crates/pyrometer/tests/no_killed_ctxs.rs index b56de34f..69c3319e 100644 --- a/crates/pyrometer/tests/no_killed_ctxs.rs +++ b/crates/pyrometer/tests/no_killed_ctxs.rs @@ -2,6 +2,14 @@ use std::env; mod helpers; use helpers::*; +#[test] +fn test_assign() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/assign.sol"); + let sol = include_str!("./test_data/assign.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_bitwise() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -10,6 +18,14 @@ fn test_bitwise() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_binop() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/bin_op.sol"); + let sol = include_str!("./test_data/bin_op.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_cast() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -18,6 +34,13 @@ fn test_cast() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_condop() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/cond_op.sol"); + assert_no_parse_errors(path_str); +} + #[test] fn test_dyn_types() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -42,6 +65,14 @@ fn test_function_calls() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_literals() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/literals.sol"); + let sol = include_str!("./test_data/literals.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_logical() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); @@ -82,6 +113,13 @@ fn test_require() { assert_no_ctx_killed(path_str, sol); } +#[test] +fn test_require_with_killed() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/require_with_killed.sol"); + assert_no_parse_errors(path_str); +} + #[test] fn test_using() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); diff --git a/crates/pyrometer/tests/test_data/assign.sol b/crates/pyrometer/tests/test_data/assign.sol new file mode 100644 index 00000000..64ce63cb --- /dev/null +++ b/crates/pyrometer/tests/test_data/assign.sol @@ -0,0 +1,20 @@ +pragma solidity ^0.8.0; + +contract Assign { + + function doAssignment() public { + // Multi-value LHS (tuple) + (uint x, uint y) = (uint16(1), 2); + + // Single value RHS + uint z = 3; + + (x, y) = (z, z); + + // uint[2] memory a = [uint(1), uint(2)]; + // uint[2] memory b = [uint(3), uint(4)]; + + + // (a, b) = (b, a); + } +} \ No newline at end of file diff --git a/crates/pyrometer/tests/test_data/bin_op.sol b/crates/pyrometer/tests/test_data/bin_op.sol new file mode 100644 index 00000000..90db5cdb --- /dev/null +++ b/crates/pyrometer/tests/test_data/bin_op.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.8.0; + +contract BinOp { + + function testBinOp(int y) public { + int x = 1; + int z = 5 + x; + int a = x * y; + } +} \ No newline at end of file diff --git a/crates/pyrometer/tests/test_data/cmp.sol b/crates/pyrometer/tests/test_data/cmp.sol new file mode 100644 index 00000000..2c7b0f32 --- /dev/null +++ b/crates/pyrometer/tests/test_data/cmp.sol @@ -0,0 +1,10 @@ +pragma solidity ^0.8.0; + +contract Cmp { + + function testCmp(int x) public { + uint a = 5; + bool b = (5 < 6) || (5 == 6 && 0 < 1); // Correct the tuple comparison + + } +} \ No newline at end of file diff --git a/crates/pyrometer/tests/test_data/cond_op.sol b/crates/pyrometer/tests/test_data/cond_op.sol new file mode 100644 index 00000000..5279d074 --- /dev/null +++ b/crates/pyrometer/tests/test_data/cond_op.sol @@ -0,0 +1,53 @@ +pragma solidity ^0.8.0; + +contract CondOp { + + function if_both_possible(uint x) public returns (uint) { + if (x > 100) { + return 1; + } else { + return 2; + } + } + + function if_first_possible() public returns (uint) { + uint x = 101; + if (x > 100) { + return 1; + } else { + return 2; + } + } + + function if_second_possible() public returns (uint) { + uint x = 99; + if (x > 100) { + return 1; + } else { + return 2; + } + } + + function if_neither_possible(uint x) public returns (uint) { + if (x > 100) { + require(x < 100); // not possible + return 1; + } else { + require(x > 100); // not possible + return 2; + } + return 0; + } + + + function ternaryParam(uint x) public returns (uint) { + uint y = x > 2 ? 1 : 2; + return y; + } + + function ternaryLiteral() public returns (uint) { + uint y = 1 > 2 ? 1 : 2; + "pyro::variable::y::range::[2,2]"; + return y; + } +} \ No newline at end of file diff --git a/crates/pyrometer/tests/test_data/dyn_types.sol b/crates/pyrometer/tests/test_data/dyn_types.sol index 81d4e01e..ce09a89e 100644 --- a/crates/pyrometer/tests/test_data/dyn_types.sol +++ b/crates/pyrometer/tests/test_data/dyn_types.sol @@ -7,6 +7,7 @@ contract DynTypes { } mapping(address => Strukt) public someMapping; + mapping(address => Strukt[]) public someMapping2; function bytes_dyn(bytes calldata x) public { bytes memory y = x; @@ -15,6 +16,7 @@ contract DynTypes { require(y.length == 9); } + function array_dyn(uint256[] memory x) public { x[0] = 5; require(x.length < 10); @@ -80,4 +82,47 @@ contract DynTypes { address holder = holders[j]; } } + + struct DontUseMoreThanOnce { + uint256 a; + uint256 b; + } + + function dynUserType() public { + DontUseMoreThanOnce[] memory dont = new DontUseMoreThanOnce[](1); + dont[0].a = 100; + dont[0].b = 100; + require(dont[0].a == 100); + } + + function getReturnedUserType() public pure { + // Strukt[] memory strukt = returnUserType()[0]; + Strukt memory strukt = returnUserType()[0]; + require(strukt.a == 100); + } + + function returnUserType() public pure returns (Strukt[] memory) { + Strukt[] memory strukt = new Strukt[](1); + strukt[0].a = 100; + strukt[0].b = 100; + return strukt; + } + + function multiDimensionalArray() public returns (bool z) { + uint256[][] memory multiArray = new uint256[][](2); + uint256[] memory indices = new uint256[](2); + + indices[0] = 0; + indices[1] = 1; + + for (uint i = 0; i < multiArray.length; i++) { + multiArray[i] = new uint256[](2); + for (uint j = 0; j < multiArray[i].length; j++) { + multiArray[i][j] = 1; + } + } + + z = true; + } + } diff --git a/crates/pyrometer/tests/test_data/env.sol b/crates/pyrometer/tests/test_data/env.sol index a11015f2..1655f2e8 100644 --- a/crates/pyrometer/tests/test_data/env.sol +++ b/crates/pyrometer/tests/test_data/env.sol @@ -6,4 +6,45 @@ contract Env { function msg_data() public returns (bytes memory) { return msg.data; } + + function testBlock() public payable { + /* + blockhash(uint blockNumber) returns (bytes32): hash of the given block - only works for 256 most recent blocks + blobhash(uint index) returns (bytes32): versioned hash of the index-th blob associated with the current transaction. A versioned hash consists of a single byte representing the version (currently 0x01), followed by the last 31 bytes of the SHA256 hash of the KZG commitment (EIP-4844). + block.basefee (uint): current block’s base fee (EIP-3198 and EIP-1559) + block.blobbasefee (uint): current block’s blob base fee (EIP-7516 and EIP-4844) + block.chainid (uint): current chain id + block.coinbase (address payable): current block miner’s address + block.difficulty (uint): current block difficulty (EVM < Paris). For other EVM versions it behaves as a deprecated alias for block.prevrandao that will be removed in the next breaking release + block.gaslimit (uint): current block gaslimit + block.number (uint): current block number + block.prevrandao (uint): random number provided by the beacon chain (EVM >= Paris) (see EIP-4399 ) + block.timestamp (uint): current block timestamp in seconds since Unix epoch + gasleft() returns (uint256): remaining gas + msg.data (bytes): complete calldata + msg.sender (address): sender of the message (current call) + msg.sig (bytes4): first four bytes of the calldata (i.e. function identifier) + msg.value (uint): number of wei sent with the message + tx.gasprice (uint): gas price of the transaction + tx.origin (address): sender of the transaction (full call chain) + */ + bytes32 a = blockhash(1); + bytes32 b = blobhash(1); + uint c = block.basefee; + uint d = block.blobbasefee; + uint e = block.chainid; + address payable f = block.coinbase; + uint g = block.difficulty; + uint h = block.gaslimit; + uint i = block.number; + uint j = block.prevrandao; + uint k = block.timestamp; + uint l = gasleft(); + bytes memory m = msg.data; + address n = msg.sender; + bytes4 o = msg.sig; + uint p = msg.value; + uint q = tx.gasprice; + address r = tx.origin; + } } diff --git a/crates/pyrometer/tests/test_data/function_calls.sol b/crates/pyrometer/tests/test_data/function_calls.sol index e687f402..d0a9faaf 100644 --- a/crates/pyrometer/tests/test_data/function_calls.sol +++ b/crates/pyrometer/tests/test_data/function_calls.sol @@ -83,3 +83,55 @@ contract ExternalFuncCalls { // function foo(address by, address from, address to, uint256 id) internal {} // } + +contract S1 { + function a(uint x) internal pure virtual returns (uint) { + return 100; + } +} + +contract S2 { + function a(uint x) internal pure virtual returns (uint) { + return 10; + } + + function b(uint x) internal pure virtual returns (uint) { + return 10; + } +} + +contract C is S1, S2 { + function supers(uint128 x) public pure returns (uint) { + uint local_a = a(1); + uint super_a = super.a(1); + require(local_a == 50); + require(super_a == 10); + + uint local_super_b = b(x); + uint super_b = super.b(x); + require(local_super_b == super_b); + return 0; + } + + function a(uint256 x) internal pure override(S1, S2) returns (uint) { + return 50; + } +} + +contract D is S2, S1 { + function supers(uint128 x) public pure returns (uint) { + uint local_a = a(1); + uint super_a = super.a(1); + require(local_a == 50); + require(super_a == 100); + + uint local_super_b = b(x); + uint super_b = super.b(x); + require(local_super_b == super_b); + return 0; + } + + function a(uint256 x) internal pure override(S1, S2) returns (uint) { + return 50; + } +} \ No newline at end of file diff --git a/crates/pyrometer/tests/test_data/literals.sol b/crates/pyrometer/tests/test_data/literals.sol new file mode 100644 index 00000000..cc138c36 --- /dev/null +++ b/crates/pyrometer/tests/test_data/literals.sol @@ -0,0 +1,39 @@ +contract Literals { + + function foo() public returns (string memory) { + uint a = 115792089237316195423570985008687907853269984665640564039457584007913129639935; // ok + // uint b = 115792089237316195423570985008687907853269984665640564039457584007913129639936; // too big + // uint c = 115792089237316195423570985008687907853269984665640564039457584007913129639935 ** 2; // too big + int d = -57896044618658097711785492504343953926634992332820282019728792003956564819968; // ok + // int e = -57896044618658097711785492504343953926634992332820282019728792003956564819969; // too big + uint f = 1.0 ** 2; // ok + // uint g = 1.5 ** 2; // not uint + uint h = 1.5 ** 0; // ok + "pyro::variable::h::range::[1,1]"; + uint256 i = 123 ** 10; // 792594609605189126649 + address w = address(0xdCad3a6d3569DF655070DEd06cb7A1b2Ccd1D3AF); + w = 0xdCad3a6d3569D_F655070DEd06cb7A_1b2Ccd1D3AF; + + uint j = 0.000000002e18; + j = 25; + j = 1.5e0 ether; + "pyro::variable::j::range::[1500000000000000000,1500000000000000000]"; + int k = -0; + k = 25.5e5; + k = -23.5e5 ether; + "pyro::variable::k::range::[-2350000000000000000000000,-2350000000000000000000000]"; + + k = -23.5e5 seconds; + k = -23.5e5 minutes; + k = -23.5e5 hours; + k = -23.5e5 days; + k = -23.5e5 weeks; + k = -0x54; + "pyro::variable::k::range::[-84,-84]"; + string memory s = unicode"🔥🔫"; // TODO unicode string values is not correct yet + + bytes memory r = hex"11111111111111111111111111111111111111111111111111111111111111111111111111111111"; + r = hex"1111111111111111111111111111111111111111" hex"111111111111111111111111111111111111111111111111"; + return s; + } +} \ No newline at end of file diff --git a/crates/pyrometer/tests/test_data/require.sol b/crates/pyrometer/tests/test_data/require.sol index e010536b..454278d0 100644 --- a/crates/pyrometer/tests/test_data/require.sol +++ b/crates/pyrometer/tests/test_data/require.sol @@ -40,10 +40,6 @@ contract Require { require(x != bytes32(hex"1337")); } - function b_ytes32_neq(bytes32 x) public { - require(x != bytes32(hex"00")); - } - function b_ytes16(bytes16 x) public { require(x == bytes16(hex"1337")); } @@ -63,4 +59,16 @@ contract Require { function b_ytes1(bytes1 x) public { require(x == bytes1(hex"13")); } + + function UintMoreEqual(uint8 x) public { + require(x >= 100); + "pyro::constraint::(x >= 100)"; + "pyro::variable::x::range::[100, 255]"; + } + + function UintLessEqual(uint8 x) public { + require(x <= 100); + "pyro::constraint::(x <= 100)"; + "pyro::variable::x::range::[0, 100]"; + } } diff --git a/crates/pyrometer/tests/test_data/require_with_killed.sol b/crates/pyrometer/tests/test_data/require_with_killed.sol new file mode 100644 index 00000000..f85937b2 --- /dev/null +++ b/crates/pyrometer/tests/test_data/require_with_killed.sol @@ -0,0 +1,109 @@ +contract RequireWithKilled { + + uint public count = 0; + uint storeRange = 0; + function setStoreRange(uint x) public { + storeRange = x; + } + + function requireLt(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x less than storeRange + require(x < storeRange); + } + + function requireLtLocal(uint x) public { + uint y = 50; + // set bounds for storeRange + require(5 < y && y < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x less than storeRange + require(x < y); + } + + function setCount() public { + count = 0; + } + function andShortCircuit() public { + count = 0; + // ( bump(false) && bump(true) ) || true , this will test that the second bump is not evaluated since the `and` short circuits + require((bumpCountIfValueEq1ThenReturn(1, false) && bumpCountIfValueEq1ThenReturn(1, true)) || true); + "pyro::variable::count::range::[1, 1]"; + } + + function andFullCircuit() public { + count = 0; + // ( bump(true) && bump(true) ) , this will test that the second bump is evaluated since the `and` does not short circuit + require(bumpCountIfValueEq1ThenReturn(1, true) && bumpCountIfValueEq1ThenReturn(1, true)); + "pyro::variable::count::range::[2, 2]"; + } + + function orShortCircuit() public { + count = 0; + // ( bump(true) || bump(true) ) , this will test that the second bump is not evaluated since the `or` short circuits + require(bumpCountIfValueEq1ThenReturn(1, true) || bumpCountIfValueEq1ThenReturn(1, true)); + "pyro::variable::count::range::[1, 1]"; + } + + function orShortCircuitRHS() public { + count = 0; + // ( bump(true) || bump(true) ) , this will test that the second bump is not evaluated since the `or` short circuits + require(bumpCountIfValueEq1ThenReturn(2, true) || bumpCountIfValueEq1ThenReturn(1, true)); + "pyro::variable::count::range::[0, 0]"; + + count = 0; + require(bumpCountIfValueEq1ThenReturn(1, true) || true); + "pyro::variable::count::range::[1, 1]"; + + count = 0; + require(true || bumpCountIfValueEq1ThenReturn(1, true)); + "pyro::variable::count::range::[0, 0]"; + } + + function yulAndFullCircuit() public { + count = 0; + assembly { + function bumpCountIfValueEq1ThenReturn(x, returnValue) -> result { + let count_val := sload(0) + // first if needs both x and count to be 0 + if and(eq(count_val, 0), eq(x, 0)) { + // add 1 to count + sstore(0, add(sload(0), 1)) + } + // second if needs both values to be 1 + if and(eq(count_val, 1), eq(x, 1)) { + // add 1 to count + sstore(0, add(sload(0), 1)) + } + result := true + } + + // in yul: rhs is evaluated, then lhs. no short circuiting + if or(bumpCountIfValueEq1ThenReturn(1, true), bumpCountIfValueEq1ThenReturn(0, true)) {} + } + "pyro::variable::count::range::[2, 2]"; + } + + function orFullCircuit() public { + count = 0; + // ( bump(false) || bump(true) ) , this will test that the second bump is evaluated since the `or` does not short circuit + require(bumpCountIfValueEq1ThenReturn(1, false) || bumpCountIfValueEq1ThenReturn(1, true)); + "pyro::variable::count::range::[2, 2]"; + } + + function bumpCountIfValueEq1ThenReturn(uint8 x, bool returnValue) internal returns (bool) { + if (x == 1) { + count += 1; + } + return returnValue; + } + +} + + + +