From ad9c1f89971cb113ad25049587592e979abf31c1 Mon Sep 17 00:00:00 2001 From: plotchy Date: Fri, 19 Jul 2024 20:19:37 -0400 Subject: [PATCH 1/7] feat: add coverage tests --- crates/pyrometer/tests/no_killed_ctxs.rs | 38 ++++++ crates/pyrometer/tests/test_data/assign.sol | 20 ++++ crates/pyrometer/tests/test_data/bin_op.sol | 10 ++ crates/pyrometer/tests/test_data/cmp.sol | 10 ++ crates/pyrometer/tests/test_data/cond_op.sol | 53 +++++++++ .../pyrometer/tests/test_data/dyn_types.sol | 45 ++++++++ crates/pyrometer/tests/test_data/env.sol | 41 +++++++ .../tests/test_data/function_calls.sol | 52 +++++++++ crates/pyrometer/tests/test_data/literals.sol | 39 +++++++ crates/pyrometer/tests/test_data/require.sol | 16 ++- .../tests/test_data/require_with_killed.sol | 109 ++++++++++++++++++ 11 files changed, 429 insertions(+), 4 deletions(-) create mode 100644 crates/pyrometer/tests/test_data/assign.sol create mode 100644 crates/pyrometer/tests/test_data/bin_op.sol create mode 100644 crates/pyrometer/tests/test_data/cmp.sol create mode 100644 crates/pyrometer/tests/test_data/cond_op.sol create mode 100644 crates/pyrometer/tests/test_data/literals.sol create mode 100644 crates/pyrometer/tests/test_data/require_with_killed.sol 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; + } + +} + + + + From 765f27a2405a034c868afdf385b321d4e1c9ba57 Mon Sep 17 00:00:00 2001 From: plotchy Date: Fri, 19 Jul 2024 20:21:23 -0400 Subject: [PATCH 2/7] adding comments for match arm examples --- crates/solc-expressions/src/array.rs | 52 +---- crates/solc-expressions/src/assign.rs | 17 +- crates/solc-expressions/src/bin_op.rs | 7 + crates/solc-expressions/src/cmp.rs | 206 +----------------- crates/solc-expressions/src/literal.rs | 1 + .../src/pre_post_in_decrement.rs | 11 +- 6 files changed, 43 insertions(+), 251 deletions(-) diff --git a/crates/solc-expressions/src/array.rs b/crates/solc-expressions/src/array.rs index 017e96ec..df294c87 100644 --- a/crates/solc-expressions/src/array.rs +++ b/crates/solc-expressions/src/array.rs @@ -48,6 +48,8 @@ pub trait Array: AnalyzerBackend + Sized { ) -> Result<(), ExprErr> { match ret { ExprRet::Single(inner_ty) | ExprRet::SingleLiteral(inner_ty) => { + // ie: uint[] + // ie: uint[][] if let Some(var_type) = VarType::try_from_idx(self, inner_ty) { let dyn_b = Builtin::Array(var_type); if let Some(idx) = self.builtins().get(&dyn_b) { @@ -65,6 +67,7 @@ pub trait Array: AnalyzerBackend + Sized { } } ExprRet::Multi(inner) => { + // ie: unsure of syntax needed to get here. (not possible?) inner .into_iter() .map(|i| self.match_ty(ctx, ty_expr, i)) @@ -180,7 +183,6 @@ pub trait Array: AnalyzerBackend + Sized { (RangeOp::Lte, RangeOp::Gte), )?; } - let name = format!( "{}[{}]", parent.name(self).into_expr_err(loc)?, @@ -470,52 +472,4 @@ pub trait Array: AnalyzerBackend + Sized { Ok(()) } } - - fn update_array_min_if_length( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - maybe_length: ContextVarNode, - ) -> Result<(), ExprErr> { - if let Some(backing_arr) = maybe_length.len_var_to_array(self).into_expr_err(loc)? { - let next_arr = self.advance_var_in_ctx( - backing_arr.latest_version_or_inherited_in_ctx(ctx, self), - loc, - ctx, - )?; - let new_len = Elem::from(backing_arr) - .get_length() - .max(maybe_length.into()); - let min = Elem::from(backing_arr).set_length(new_len); - next_arr - .set_range_min(self, arena, min) - .into_expr_err(loc)?; - } - Ok(()) - } - - fn update_array_max_if_length( - &mut self, - arena: &mut RangeArena>, - ctx: ContextNode, - loc: Loc, - maybe_length: ContextVarNode, - ) -> Result<(), ExprErr> { - if let Some(backing_arr) = maybe_length.len_var_to_array(self).into_expr_err(loc)? { - let next_arr = self.advance_var_in_ctx( - backing_arr.latest_version_or_inherited_in_ctx(ctx, self), - loc, - ctx, - )?; - let new_len = Elem::from(backing_arr) - .get_length() - .min(maybe_length.into()); - let max = Elem::from(backing_arr).set_length(new_len); - next_arr - .set_range_max(self, arena, max) - .into_expr_err(loc)?; - } - Ok(()) - } } diff --git a/crates/solc-expressions/src/assign.rs b/crates/solc-expressions/src/assign.rs index bf6787fc..3a123c87 100644 --- a/crates/solc-expressions/src/assign.rs +++ b/crates/solc-expressions/src/assign.rs @@ -77,6 +77,7 @@ pub trait Assign: AnalyzerBackend + Sized Ok(()) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: uint x = 5; let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -86,6 +87,7 @@ pub trait Assign: AnalyzerBackend + Sized Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: uint x = y; let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -94,23 +96,31 @@ pub trait Assign: AnalyzerBackend + Sized .into_expr_err(loc)?; Ok(()) } - (l @ ExprRet::Single(_), ExprRet::Multi(rhs_sides)) => rhs_sides - .iter() - .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, l, expr_ret)), + (l @ ExprRet::Single(_), ExprRet::Multi(rhs_sides)) => { + // ie: uint x = (a, b), not possible? + rhs_sides + .iter() + .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, l, expr_ret)) + } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_) | r @ ExprRet::SingleLiteral(_)) => { + // ie: (uint x, uint y) = a, not possible? lhs_sides .iter() .try_for_each(|expr_ret| self.match_assign_sides(arena, ctx, loc, expr_ret, r)) } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { // try to zip sides if they are the same length + // (x, y) = (a, b) + // ie: (x, y) = (a, b, c), not possible? if lhs_sides.len() == rhs_sides.len() { + // (x, y) = (a, b) lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { self.match_assign_sides(arena, ctx, loc, lhs_expr_ret, rhs_expr_ret) }, ) } else { + // ie: (x, y) = (a, b, c), not possible? rhs_sides.iter().try_for_each(|rhs_expr_ret| { self.match_assign_sides(arena, ctx, loc, lhs_paths, rhs_expr_ret) }) @@ -203,6 +213,7 @@ pub trait Assign: AnalyzerBackend + Sized } if !lhs_cvar.ty_eq(&rhs_cvar, self).into_expr_err(loc)? { + // lhs type doesnt match rhs type (not possible? have never reached this) let cast_to_min = match lhs_cvar.range_min(self).into_expr_err(loc)? { Some(v) => v, None => { diff --git a/crates/solc-expressions/src/bin_op.rs b/crates/solc-expressions/src/bin_op.rs index 01c02663..bad31ca5 100644 --- a/crates/solc-expressions/src/bin_op.rs +++ b/crates/solc-expressions/src/bin_op.rs @@ -75,6 +75,7 @@ pub trait BinOp: AnalyzerBackend + Sized { "No right hand side provided for binary operation".to_string(), )), (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: 5 + 5 let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -89,6 +90,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { + // ie: 5 + x ContextVarNode::from(*lhs) .cast_from(&ContextVarNode::from(*rhs), self, arena) .into_expr_err(loc)?; @@ -104,6 +106,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: x + 5 ContextVarNode::from(*rhs) .cast_from(&ContextVarNode::from(*lhs), self, arena) .into_expr_err(loc)?; @@ -119,6 +122,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: x + y let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -131,6 +135,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (lhs @ ExprRet::Single(..), ExprRet::Multi(rhs_sides)) => { + // ie: x + (y, z), (not possible?) rhs_sides .iter() .map(|expr_ret| self.op_match(arena, ctx, loc, lhs, expr_ret, op, assign)) @@ -138,6 +143,7 @@ pub trait BinOp: AnalyzerBackend + Sized { Ok(()) } (ExprRet::Multi(lhs_sides), rhs @ ExprRet::Single(..)) => { + // ie: (x, y) + z, (not possible?) lhs_sides .iter() .map(|expr_ret| self.op_match(arena, ctx, loc, expr_ret, rhs, op, assign)) @@ -147,6 +153,7 @@ pub trait BinOp: AnalyzerBackend + Sized { (_, ExprRet::CtxKilled(kind)) => ctx.kill(self, loc, *kind).into_expr_err(loc), (ExprRet::CtxKilled(kind), _) => ctx.kill(self, loc, *kind).into_expr_err(loc), (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => Err(ExprErr::UnhandledCombo( + // ie: (x, y) + (a, b), (not possible?) loc, format!("Unhandled combination in binop: {lhs_sides:?} {rhs_sides:?}"), )), diff --git a/crates/solc-expressions/src/cmp.rs b/crates/solc-expressions/src/cmp.rs index 59f81700..8cbee813 100644 --- a/crates/solc-expressions/src/cmp.rs +++ b/crates/solc-expressions/src/cmp.rs @@ -165,12 +165,14 @@ pub trait Cmp: AnalyzerBackend + Sized { match (lhs_paths, rhs_paths) { (_, ExprRet::Null) | (ExprRet::Null, _) => Ok(()), (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { + // ie: 5 == x ContextVarNode::from(*lhs) .literal_cast_from(&ContextVarNode::from(*rhs), self) .into_expr_err(loc)?; self.cmp_inner(arena, ctx, loc, &ExprRet::Single(*rhs), op, rhs_paths) } (ExprRet::SingleLiteral(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: 5 == 5 let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let rhs_cvar = @@ -187,12 +189,14 @@ pub trait Cmp: AnalyzerBackend + Sized { ) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: x == 5 ContextVarNode::from(*rhs) .literal_cast_from(&ContextVarNode::from(*lhs), self) .into_expr_err(loc)?; self.cmp_inner(arena, ctx, loc, lhs_paths, op, &ExprRet::Single(*rhs)) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: x == y let lhs_cvar = ContextVarNode::from(*lhs); let rhs_cvar = ContextVarNode::from(*rhs); tracing::trace!( @@ -260,12 +264,14 @@ pub trait Cmp: AnalyzerBackend + Sized { .into_expr_err(loc) } (l @ ExprRet::Single(_lhs), ExprRet::Multi(rhs_sides)) => { + // ie: x == [y, z] (not possible?) rhs_sides .iter() .try_for_each(|expr_ret| self.cmp_inner(arena, ctx, loc, l, op, expr_ret))?; Ok(()) } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_)) => { + // ie: (x, y) == z (not possible?) lhs_sides .iter() .try_for_each(|expr_ret| self.cmp_inner(arena, ctx, loc, expr_ret, op, r))?; @@ -273,7 +279,10 @@ pub trait Cmp: AnalyzerBackend + Sized { } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { // try to zip sides if they are the same length + // ie: (x, y) == (a, b) (not possible?) + // ie: (x, y, z) == (a, b) (not possible?) if lhs_sides.len() == rhs_sides.len() { + // ie: (x, y) == (a, b) (not possible?) lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { self.cmp_inner(arena, ctx, loc, lhs_expr_ret, op, rhs_expr_ret) @@ -281,6 +290,7 @@ pub trait Cmp: AnalyzerBackend + Sized { )?; Ok(()) } else { + // ie: (x, y, z) == (a, b) (not possible?) rhs_sides.iter().try_for_each(|rhs_expr_ret| { self.cmp_inner(arena, ctx, loc, lhs_paths, op, rhs_expr_ret) })?; @@ -293,200 +303,4 @@ pub trait Cmp: AnalyzerBackend + Sized { )), } } - - // fn not_eval( - // &mut self, - // _ctx: ContextNode, - // loc: Loc, - // lhs_cvar: ContextVarNode, - // ) -> Result { - // if let Some(lhs_range) = lhs_cvar.ref_range(self).into_expr_err(loc)? { - // let lhs_min = lhs_range.evaled_range_min(self, arena).into_expr_err(loc)?; - - // // invert - // if lhs_min.range_eq(&lhs_range.minimize(self, arena).into_expr_err(loc)?, self) { - // let val = Elem::Expr(RangeExpr::new( - // lhs_range.range_min().into_owned(), - // RangeOp::Not, - // Elem::Null, - // )); - - // return Ok(SolcRange::new(val.clone(), val, lhs_range.exclusions.clone())); - // } - // } - - // let min = Elem::Concrete(RangeConcrete { - // val: Concrete::Bool(false), - // loc, - // }).arenaize(self); - - // let max = Elem::Concrete(RangeConcrete { - // val: Concrete::Bool(true), - // loc, - // }).arenaize(self); - // Ok(SolcRange::new( - // min, - // max, - // vec![], - // )) - // } - - fn range_eval( - &self, - arena: &mut RangeArena>, - _ctx: ContextNode, - lhs_cvar: ContextVarNode, - rhs_cvar: ContextVarNode, - op: RangeOp, - ) -> Result { - if let Some(lhs_range) = lhs_cvar.ref_range(self)? { - if let Some(rhs_range) = rhs_cvar.ref_range(self)? { - match op { - RangeOp::Lt => { - // if lhs_max < rhs_min, we know this cmp will evaluate to - // true - - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, arena) { - return Ok(true.into()); - } - - // Similarly if lhs_min >= rhs_max, we know this cmp will evaluate to - // false - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - match lhs_min.range_ord(&rhs_max, arena) { - Some(Ordering::Greater) => { - return Ok(false.into()); - } - Some(Ordering::Equal) => { - return Ok(false.into()); - } - _ => {} - } - } - RangeOp::Gt => { - // if lhs_min > rhs_max, we know this cmp will evaluate to - // true - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, arena) { - return Ok(true.into()); - } - - // if lhs_max <= rhs_min, we know this cmp will evaluate to - // false - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - match lhs_max.range_ord(&rhs_min, arena) { - Some(Ordering::Less) => { - return Ok(false.into()); - } - Some(Ordering::Equal) => { - return Ok(false.into()); - } - _ => {} - } - } - RangeOp::Lte => { - // if lhs_max <= rhs_min, we know this cmp will evaluate to - // true - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - match lhs_max.range_ord(&rhs_min, arena) { - Some(Ordering::Less) => { - return Ok(true.into()); - } - Some(Ordering::Equal) => { - return Ok(true.into()); - } - _ => {} - } - - // Similarly if lhs_min > rhs_max, we know this cmp will evaluate to - // false - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let Some(Ordering::Greater) = lhs_min.range_ord(&rhs_max, arena) { - return Ok(false.into()); - } - } - RangeOp::Gte => { - // if lhs_min >= rhs_max, we know this cmp will evaluate to - // true - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - match lhs_min.range_ord(&rhs_max, arena) { - Some(Ordering::Greater) => { - return Ok(true.into()); - } - Some(Ordering::Equal) => { - return Ok(true.into()); - } - _ => {} - } - - // if lhs_max < rhs_min, we know this cmp will evaluate to - // false - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - if let Some(Ordering::Less) = lhs_max.range_ord(&rhs_min, arena) { - return Ok(false.into()); - } - } - RangeOp::Eq => { - // if all elems are equal we know its true - // we dont know anything else - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let ( - Some(Ordering::Equal), - Some(Ordering::Equal), - Some(Ordering::Equal), - ) = ( - // check lhs_min == lhs_max, ensures lhs is const - lhs_min.range_ord(&lhs_max, arena), - // check lhs_min == rhs_min, checks if lhs == rhs - lhs_min.range_ord(&rhs_min, arena), - // check rhs_min == rhs_max, ensures rhs is const - rhs_min.range_ord(&rhs_max, arena), - ) { - return Ok(true.into()); - } - } - RangeOp::Neq => { - // if all elems are equal we know its true - // we dont know anything else - let lhs_min = lhs_range.evaled_range_min(self, arena)?; - let lhs_max = lhs_range.evaled_range_max(self, arena)?; - let rhs_min = rhs_range.evaled_range_min(self, arena)?; - let rhs_max = rhs_range.evaled_range_max(self, arena)?; - if let ( - Some(Ordering::Equal), - Some(Ordering::Equal), - Some(Ordering::Equal), - ) = ( - // check lhs_min == lhs_max, ensures lhs is const - lhs_min.range_ord(&lhs_max, arena), - // check lhs_min == rhs_min, checks if lhs == rhs - lhs_min.range_ord(&rhs_min, arena), - // check rhs_min == rhs_max, ensures rhs is const - rhs_min.range_ord(&rhs_max, arena), - ) { - return Ok(false.into()); - } - } - e => unreachable!("Cmp with strange op: {:?}", e), - } - Ok(SolcRange::default_bool()) - } else { - Ok(SolcRange::default_bool()) - } - } else { - Ok(SolcRange::default_bool()) - } - } } diff --git a/crates/solc-expressions/src/literal.rs b/crates/solc-expressions/src/literal.rs index 6ced7c03..a5c2a2f4 100644 --- a/crates/solc-expressions/src/literal.rs +++ b/crates/solc-expressions/src/literal.rs @@ -251,6 +251,7 @@ pub trait Literal: AnalyzerBackend + Sized { }); ConcreteNode::from(self.add_node(Node::Concrete(Concrete::Bytes(max, target)))) } else { + // hex"" ConcreteNode::from(self.add_node(Node::Concrete(Concrete::DynBytes(h)))) }; diff --git a/crates/solc-expressions/src/pre_post_in_decrement.rs b/crates/solc-expressions/src/pre_post_in_decrement.rs index 9550c32d..98a47c36 100644 --- a/crates/solc-expressions/src/pre_post_in_decrement.rs +++ b/crates/solc-expressions/src/pre_post_in_decrement.rs @@ -135,12 +135,14 @@ pub trait PrePostIncDecrement: Ok(()) } ExprRet::SingleLiteral(var) => { + // ie: 5++; (not valid syntax) ContextVarNode::from(*var) .try_increase_size(self, arena) .into_expr_err(loc)?; self.match_in_de_crement(arena, ctx, pre, increment, loc, &ExprRet::Single(*var)) } ExprRet::Single(var) => { + // ie: a++; let cvar = ContextVarNode::from(*var).latest_version_or_inherited_in_ctx(ctx, self); let elem = Elem::from(cvar); let one = Elem::from(Concrete::from(U256::from(1))).cast(elem.clone()); @@ -228,9 +230,12 @@ pub trait PrePostIncDecrement: Ok(()) } } - ExprRet::Multi(inner) => inner.iter().try_for_each(|expr| { - self.match_in_de_crement(arena, ctx, pre, increment, loc, expr) - }), + ExprRet::Multi(inner) => { + // ie: (5, 5)++; (invalid syntax) + inner.iter().try_for_each(|expr| { + self.match_in_de_crement(arena, ctx, pre, increment, loc, expr) + }) + } ExprRet::Null => Ok(()), } } From d200403cf681564ce031fb4ca8d9bea672a6dc94 Mon Sep 17 00:00:00 2001 From: plotchy Date: Fri, 19 Jul 2024 20:50:38 -0400 Subject: [PATCH 3/7] more require branch matches --- .../tests/test_data/require_with_killed.sol | 47 +++++++++++++++++-- crates/solc-expressions/src/require.rs | 9 ++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/crates/pyrometer/tests/test_data/require_with_killed.sol b/crates/pyrometer/tests/test_data/require_with_killed.sol index f85937b2..2a2c2a40 100644 --- a/crates/pyrometer/tests/test_data/require_with_killed.sol +++ b/crates/pyrometer/tests/test_data/require_with_killed.sol @@ -9,20 +9,57 @@ contract RequireWithKilled { function requireLt(uint x) public { // set bounds for storeRange require(5 < storeRange && storeRange < 100); + "pyro::variable::storeRange::range::[6, 99]"; // set tighter bounds for x require(6 < x && x < 99); + "pyro::variable::x::range::[7, 98]"; // make x less than storeRange require(x < storeRange); } - function requireLtLocal(uint x) public { - uint y = 50; + function requireLte(uint x) public { // set bounds for storeRange - require(5 < y && y < 100); + require(5 < storeRange && storeRange < 100); // set tighter bounds for x require(6 < x && x < 99); - // make x less than storeRange - require(x < y); + // make x less than or equal to storeRange + require(x <= storeRange); + } + + function requireGt(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x greater than storeRange + require(x > storeRange); + } + + function requireGte(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x greater than or equal to storeRange + require(x >= storeRange); + } + + function requireEq(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x equal to storeRange + require(x == storeRange); + } + + function requireNeq(uint x) public { + // set bounds for storeRange + require(5 < storeRange && storeRange < 100); + // set tighter bounds for x + require(6 < x && x < 99); + // make x not equal to storeRange + require(x != storeRange); } function setCount() public { diff --git a/crates/solc-expressions/src/require.rs b/crates/solc-expressions/src/require.rs index f8117774..73467ee8 100644 --- a/crates/solc-expressions/src/require.rs +++ b/crates/solc-expressions/src/require.rs @@ -624,6 +624,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { (_, ExprRet::Null) | (ExprRet::Null, _) => Ok(()), (_, ExprRet::CtxKilled(..)) | (ExprRet::CtxKilled(..), _) => Ok(()), (ExprRet::SingleLiteral(lhs), ExprRet::Single(rhs)) => { + // ie: require(5 == a); ContextVarNode::from(*lhs) .cast_from(&ContextVarNode::from(*rhs), self, arena) .into_expr_err(loc)?; @@ -639,6 +640,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ) } (ExprRet::Single(lhs), ExprRet::SingleLiteral(rhs)) => { + // ie: require(a == 5); ContextVarNode::from(*rhs) .cast_from(&ContextVarNode::from(*lhs), self, arena) .into_expr_err(loc)?; @@ -654,6 +656,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { ) } (ExprRet::Single(lhs), ExprRet::Single(rhs)) => { + // ie: require(a == b); let lhs_cvar = ContextVarNode::from(*lhs).latest_version_or_inherited_in_ctx(ctx, self); let new_lhs = self.advance_var_in_ctx(lhs_cvar, loc, ctx)?; @@ -665,6 +668,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { Ok(()) } (l @ ExprRet::Single(_) | l @ ExprRet::SingleLiteral(_), ExprRet::Multi(rhs_sides)) => { + // ie: require(a == (b, c)); (not possible) rhs_sides.iter().try_for_each(|expr_ret| { self.handle_require_inner( arena, @@ -679,6 +683,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } (ExprRet::Multi(lhs_sides), r @ ExprRet::Single(_) | r @ ExprRet::SingleLiteral(_)) => { + // ie: require((a, b) == c); (not possible) lhs_sides.iter().try_for_each(|expr_ret| { self.handle_require_inner( arena, @@ -693,8 +698,11 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }) } (ExprRet::Multi(lhs_sides), ExprRet::Multi(rhs_sides)) => { + // ie: require((a, b) == (c, d)); (not possible) + // ie: require((a, b) == (c, d, e)); (not possible) // try to zip sides if they are the same length if lhs_sides.len() == rhs_sides.len() { + // ie: require((a, b) == (c, d)); (not possible) lhs_sides.iter().zip(rhs_sides.iter()).try_for_each( |(lhs_expr_ret, rhs_expr_ret)| { self.handle_require_inner( @@ -710,6 +718,7 @@ pub trait Require: AnalyzerBackend + Variable + BinOp + Sized { }, ) } else { + // ie: require((a, b) == (c, d, e)); (not possible) rhs_sides.iter().try_for_each(|rhs_expr_ret| { self.handle_require_inner( arena, From 88d6a953ff70abd94dbf2723175c10a366f8bd43 Mon Sep 17 00:00:00 2001 From: plotchy Date: Mon, 22 Jul 2024 13:44:48 -0400 Subject: [PATCH 4/7] variable coverage --- crates/pyrometer/tests/no_killed_ctxs.rs | 8 ++++ crates/pyrometer/tests/test_data/variable.sol | 44 +++++++++++++++++++ 2 files changed, 52 insertions(+) create mode 100644 crates/pyrometer/tests/test_data/variable.sol diff --git a/crates/pyrometer/tests/no_killed_ctxs.rs b/crates/pyrometer/tests/no_killed_ctxs.rs index 69c3319e..7d252904 100644 --- a/crates/pyrometer/tests/no_killed_ctxs.rs +++ b/crates/pyrometer/tests/no_killed_ctxs.rs @@ -222,3 +222,11 @@ fn test_repros() { assert_no_parse_errors(path_str); } } + +#[test] +fn test_variable() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/variable.sol"); + let sol = include_str!("./test_data/variable.sol"); + assert_no_ctx_killed(path_str, sol); +} diff --git a/crates/pyrometer/tests/test_data/variable.sol b/crates/pyrometer/tests/test_data/variable.sol new file mode 100644 index 00000000..846fd356 --- /dev/null +++ b/crates/pyrometer/tests/test_data/variable.sol @@ -0,0 +1,44 @@ +contract Variable { + aUserType a_user_type; + + struct aUserType { + uint aUserType; + } + + function a_user_type_memory(aUserType memory a_user_type) public returns (uint) { + return a_user_type.aUserType; + } + + function a_user_type_calldata(aUserType calldata a_user_type) public returns (uint) { + return a_user_type.aUserType; + } + + function a_user_type_storage() public returns (uint) { + aUserType storage a_user_type = a_user_type; + return a_user_type.aUserType; + } +} + +contract B { + struct A { + address a; + } +} +contract A is B { + A a; // contract A + + function return_struct() external returns (A memory) { + // a is of type B.A, *not* Contract::A + a = A(address(this)); + return a; + } +} + +contract C { + C c; + function return_contract() external returns (C) { + // c is of type Contract::C + c = C(address(this)); + return c; + } +} \ No newline at end of file From 801de39177bb6bdf49588304b469f57c0320c58f Mon Sep 17 00:00:00 2001 From: plotchy Date: Mon, 22 Jul 2024 13:58:17 -0400 Subject: [PATCH 5/7] tests for delete --- crates/pyrometer/tests/no_killed_ctxs.rs | 9 ++ crates/pyrometer/tests/test_data/delete.sol | 138 ++++++++++++++++++++ 2 files changed, 147 insertions(+) create mode 100644 crates/pyrometer/tests/test_data/delete.sol diff --git a/crates/pyrometer/tests/no_killed_ctxs.rs b/crates/pyrometer/tests/no_killed_ctxs.rs index 7d252904..0da44405 100644 --- a/crates/pyrometer/tests/no_killed_ctxs.rs +++ b/crates/pyrometer/tests/no_killed_ctxs.rs @@ -41,6 +41,15 @@ fn test_condop() { assert_no_parse_errors(path_str); } + +#[test] +fn test_delete() { + let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); + let path_str = format!("{manifest_dir}/tests/test_data/delete.sol"); + let sol = include_str!("./test_data/delete.sol"); + assert_no_ctx_killed(path_str, sol); +} + #[test] fn test_dyn_types() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap(); diff --git a/crates/pyrometer/tests/test_data/delete.sol b/crates/pyrometer/tests/test_data/delete.sol new file mode 100644 index 00000000..91e2a42b --- /dev/null +++ b/crates/pyrometer/tests/test_data/delete.sol @@ -0,0 +1,138 @@ +pragma solidity ^0.8.0; + +contract ComplexDelete { + struct ContactInfo { + string email; + string phone; + } + + struct Address { + string street; + string city; + string country; + uint256 postalCode; + } + + struct Employment { + string company; + string position; + uint256 startDate; + uint256 endDate; + } + + struct Education { + string institution; + string degree; + uint256 graduationYear; + } + + struct User { + uint256 id; + string name; + ContactInfo contactInfo; + Address[] addresses; + Employment[] employmentHistory; + Education[] educationHistory; + mapping(string => bool) preferences; + } + + mapping(uint256 => User) public users; + uint256[] public userIds; + + function addUser( + uint256 id, + string memory name, + string memory email, + string memory phone + ) public { + require(users[id].id == 0, "User already exists"); + User storage newUser = users[id]; + newUser.id = id; + newUser.name = name; + newUser.contactInfo = ContactInfo(email, phone); + userIds.push(id); + } + + function addUserAddress( + uint256 userId, + string memory street, + string memory city, + string memory country, + uint256 postalCode + ) public { + users[userId].addresses.push(Address(street, city, country, postalCode)); + } + + function addEmploymentHistory( + uint256 userId, + string memory company, + string memory position, + uint256 startDate, + uint256 endDate + ) public { + users[userId].employmentHistory.push(Employment(company, position, startDate, endDate)); + } + + function addEducationHistory( + uint256 userId, + string memory institution, + string memory degree, + uint256 graduationYear + ) public { + users[userId].educationHistory.push(Education(institution, degree, graduationYear)); + } + + function setUserPreference(uint256 userId, string memory key, bool value) public { + users[userId].preferences[key] = value; + } + + function deleteUser(uint256 userId) public { + require(users[userId].id != 0, "User does not exist"); + delete users[userId]; + for (uint256 i = 0; i < userIds.length; i++) { + if (userIds[i] == userId) { + userIds[i] = userIds[userIds.length - 1]; + userIds.pop(); + break; + } + } + } + + function deleteUserAddress(uint256 userId, uint256 addressIndex) public { + require(addressIndex < users[userId].addresses.length, "Address index out of bounds"); + users[userId].addresses[addressIndex] = users[userId].addresses[users[userId].addresses.length - 1]; + users[userId].addresses.pop(); + } + + function deleteEmploymentHistory(uint256 userId, uint256 employmentIndex) public { + require(employmentIndex < users[userId].employmentHistory.length, "Employment index out of bounds"); + users[userId].employmentHistory[employmentIndex] = users[userId].employmentHistory[users[userId].employmentHistory.length - 1]; + users[userId].employmentHistory.pop(); + } + + function deleteEducationHistory(uint256 userId, uint256 educationIndex) public { + require(educationIndex < users[userId].educationHistory.length, "Education index out of bounds"); + users[userId].educationHistory[educationIndex] = users[userId].educationHistory[users[userId].educationHistory.length - 1]; + users[userId].educationHistory.pop(); + } + + function deleteUserPreference(uint256 userId, string memory key) public { + delete users[userId].preferences[key]; + } + + function updateContactInfo(uint256 userId, string memory newEmail, string memory newPhone) public { + users[userId].contactInfo = ContactInfo(newEmail, newPhone); + } + + function clearAllUserAddresses(uint256 userId) public { + delete users[userId].addresses; + } + + function clearAllEmploymentHistory(uint256 userId) public { + delete users[userId].employmentHistory; + } + + function clearAllEducationHistory(uint256 userId) public { + delete users[userId].educationHistory; + } +} \ No newline at end of file From 84c861b9541cabd670b96c5fa71e87059dde3f18 Mon Sep 17 00:00:00 2001 From: plotchy Date: Mon, 22 Jul 2024 14:02:41 -0400 Subject: [PATCH 6/7] prep delete for "pyro" test cases --- crates/pyrometer/tests/test_data/delete.sol | 67 +++++++++++++++++++++ 1 file changed, 67 insertions(+) diff --git a/crates/pyrometer/tests/test_data/delete.sol b/crates/pyrometer/tests/test_data/delete.sol index 91e2a42b..685175b5 100644 --- a/crates/pyrometer/tests/test_data/delete.sol +++ b/crates/pyrometer/tests/test_data/delete.sol @@ -135,4 +135,71 @@ contract ComplexDelete { function clearAllEducationHistory(uint256 userId) public { delete users[userId].educationHistory; } + +} + +contract UseComplexDelete { + ComplexDelete t; + + constructor() { + t = new ComplexDelete(); + } + + function useIt() public { + // Add users + t.addUser(1, "Alice", "alice@example.com", "1234567890"); + t.addUser(2, "Bob", "bob@example.com", "0987654321"); + + // Add addresses + t.addUserAddress(1, "123 Main St", "New York", "USA", 10001); + t.addUserAddress(1, "456 Elm St", "Los Angeles", "USA", 90001); + t.addUserAddress(2, "789 Oak St", "Chicago", "USA", 60601); + + // Add employment history + t.addEmploymentHistory(1, "TechCorp", "Developer", 1609459200, 1640995200); + t.addEmploymentHistory(1, "WebSoft", "Senior Developer", 1641081600, 0); + t.addEmploymentHistory(2, "DataFirm", "Analyst", 1577836800, 0); + + // Add education history + t.addEducationHistory(1, "Tech University", "BSc Computer Science", 2020); + t.addEducationHistory(2, "Data College", "MSc Data Science", 2019); + + // Set preferences + t.setUserPreference(1, "receiveNewsletter", true); + t.setUserPreference(1, "darkMode", false); + t.setUserPreference(2, "receiveNewsletter", false); + + // Test deletions and updates + + // Delete an address + t.deleteUserAddress(1, 0); + + // Delete employment history + t.deleteEmploymentHistory(1, 0); + + // Delete education history + t.deleteEducationHistory(2, 0); + + // Delete user preference + t.deleteUserPreference(1, "darkMode"); + + // Update contact info + t.updateContactInfo(2, "bob.new@example.com", "1122334455"); + + // Clear all addresses for a user + t.clearAllUserAddresses(1); + + // Clear all employment history for a user + t.clearAllEmploymentHistory(2); + + // Clear all education history for a user + t.clearAllEducationHistory(1); + + // Delete an entire user + t.deleteUser(1); + + // Add a new user to test after deletions + t.addUser(3, "Charlie", "charlie@example.com", "5556667777"); + t.addUserAddress(3, "321 Pine St", "San Francisco", "USA", 94101); + } } \ No newline at end of file From 6cfed05c877595a13953c49d87977dbc2a4de1d5 Mon Sep 17 00:00:00 2001 From: plotchy Date: Mon, 22 Jul 2024 14:15:56 -0400 Subject: [PATCH 7/7] lint --- crates/pyrometer/tests/no_killed_ctxs.rs | 1 - 1 file changed, 1 deletion(-) diff --git a/crates/pyrometer/tests/no_killed_ctxs.rs b/crates/pyrometer/tests/no_killed_ctxs.rs index 0da44405..b9beb7d4 100644 --- a/crates/pyrometer/tests/no_killed_ctxs.rs +++ b/crates/pyrometer/tests/no_killed_ctxs.rs @@ -41,7 +41,6 @@ fn test_condop() { assert_no_parse_errors(path_str); } - #[test] fn test_delete() { let manifest_dir = env::var("CARGO_MANIFEST_DIR").unwrap();