Skip to content

Commit dc1b287

Browse files
committed
feat: int support
1 parent ed6142a commit dc1b287

File tree

3 files changed

+63
-10
lines changed

3 files changed

+63
-10
lines changed

crates/evm/fuzz/src/strategies/param.rs

Lines changed: 16 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -225,11 +225,22 @@ pub fn fuzz_param_from_state(
225225
.boxed(),
226226
1..=31 => value()
227227
.prop_map(move |value| {
228-
// Generate a uintN in the correct range, then shift it to the range of intN
229-
// by subtracting 2^(N-1)
230-
let uint = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
231-
let max_int_plus1 = U256::from(1).wrapping_shl(n - 1);
232-
let num = I256::from_raw(uint.wrapping_sub(max_int_plus1));
228+
// Extract lower N bits
229+
let uint_n = U256::from_be_bytes(value.0) % U256::from(1).wrapping_shl(n);
230+
231+
// Interpret as signed integer (two's complement)
232+
// Check if the sign bit (bit N-1) is set
233+
let sign_bit = U256::from(1) << (n - 1);
234+
let num = if uint_n >= sign_bit {
235+
// Negative number in two's complement
236+
// Map [2^(N-1), 2^N) to [-2^(N-1), 0) by subtracting 2^N
237+
let modulus = U256::from(1) << n;
238+
I256::from_raw(uint_n.wrapping_sub(modulus))
239+
} else {
240+
// Positive number
241+
I256::from_raw(uint_n)
242+
};
243+
233244
DynSolValue::Int(num, n)
234245
})
235246
.boxed(),

crates/evm/fuzz/src/strategies/state.rs

Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -550,6 +550,37 @@ impl<'ast> ast::Visit<'ast> for LiteralsCollector {
550550
return ControlFlow::Break(());
551551
}
552552

553+
// Handle unary negation of number literals
554+
if let ast::ExprKind::Unary(un_op, inner_expr) = &expr.kind
555+
&& un_op.kind == ast::UnOpKind::Neg
556+
&& let ast::ExprKind::Lit(lit, _) = &inner_expr.kind
557+
&& let ast::LitKind::Number(n) = &lit.kind
558+
{
559+
// Compute the negative I256 value
560+
if let Ok(pos_i256) = I256::try_from(*n) {
561+
let neg_value = -pos_i256;
562+
let neg_b256 = B256::from(neg_value.into_raw());
563+
564+
// Store under all intN sizes that can represent this value
565+
for bits in [16, 32, 64, 128, 256] {
566+
if can_fit_in_int(neg_value, bits) {
567+
if self
568+
.output
569+
.words
570+
.entry(DynSolType::Int(bits))
571+
.or_default()
572+
.insert(neg_b256)
573+
{
574+
self.total_values += 1;
575+
}
576+
}
577+
}
578+
}
579+
580+
// Continue walking the expression
581+
return self.walk_expr(expr);
582+
}
583+
553584
if let ast::ExprKind::Lit(lit, _) = &expr.kind
554585
&& let Some((ty, value)) = convert_literal(lit)
555586
{
@@ -589,6 +620,17 @@ impl<'ast> ast::Visit<'ast> for LiteralsCollector {
589620
}
590621
}
591622

623+
/// Checks if a signed integer value can fit in intN type.
624+
fn can_fit_in_int(value: I256, bits: usize) -> bool {
625+
// Calculate the maximum positive value for intN: 2^(N-1) - 1
626+
let max_val = I256::try_from((U256::from(1) << (bits - 1)) - U256::from(1))
627+
.expect("max value should fit in I256");
628+
// Calculate the minimum negative value for intN: -2^(N-1)
629+
let min_val = -max_val - I256::ONE;
630+
631+
value >= min_val && value <= max_val
632+
}
633+
592634
fn convert_literal(lit: &ast::Lit<'_>) -> Option<(DynSolType, LitTy)> {
593635
use ast::LitKind;
594636

crates/forge/tests/cli/test_cmd.rs

Lines changed: 5 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -4176,8 +4176,8 @@ forgetest_init!(should_fuzz_literals, |prj, cmd| {
41764176
function checkWord(bytes32 v) external pure { assert(v != MAGIC_WORD); }
41774177
function checkNumber(uint256 v) external pure { assert(v != MAGIC_NUMBER); }
41784178
function checkInteger(int32 v) external pure { assert(v != MAGIC_INT); }
4179-
function checkBytes(bytes memory v) external pure { assert(keccak256(v) != keccak256(MAGIC_BYTES)); }
41804179
function checkString(string memory v) external pure { assert(keccak256(abi.encodePacked(v)) != keccak256(abi.encodePacked(MAGIC_STRING))); }
4180+
function checkBytesFromHex(bytes memory v) external pure { assert(keccak256(v) != keccak256(MAGIC_BYTES)); }
41814181
function checkBytesFromString(bytes memory v) external pure { assert(keccak256(v) != keccak256(abi.encodePacked(MAGIC_STRING))); }
41824182
}
41834183
"#,
@@ -4197,9 +4197,9 @@ forgetest_init!(should_fuzz_literals, |prj, cmd| {
41974197
function testFuzz_Number(uint256 v) public view { magic.checkNumber(v); }
41984198
function testFuzz_Integer(int32 v) public view { magic.checkInteger(v); }
41994199
function testFuzz_Word(bytes32 v) public view { magic.checkWord(v); }
4200-
function testFuzz_BytesFromHex(bytes memory v) public view { magic.checkBytes(v); }
4201-
function testFuzz_BytesFromString(bytes memory v) public view { magic.checkBytesFromString(v); }
42024200
function testFuzz_String(string memory v) public view { magic.checkString(v); }
4201+
function testFuzz_BytesFromHex(bytes memory v) public view { magic.checkBytesFromHex(v); }
4202+
function testFuzz_BytesFromString(bytes memory v) public view { magic.checkBytesFromString(v); }
42034203
}
42044204
"#,
42054205
);
@@ -4231,7 +4231,7 @@ Encountered a total of 1 failing tests, 0 tests succeeded
42314231
expected_runs: u32| {
42324232
prj.clear_cache_dir();
42334233

4234-
// the fuzzer is UNABLE to find a breaking input when NOT seeding from the AST
4234+
// the fuzzer is UNABLE to find a breaking input (fast) when NOT seeding from the AST
42354235
prj.update_config(|config| {
42364236
config.fuzz.runs = 100;
42374237
config.fuzz.dictionary.max_fuzz_dictionary_literals = 0;
@@ -4253,7 +4253,7 @@ Encountered a total of 1 failing tests, 0 tests succeeded
42534253

42544254
test_literal(100, "testFuzz_Addr", "address", "0x6B175474E89094C44Da98b954EedeAC495271d0F", 28);
42554255
test_literal(200, "testFuzz_Number", "uint256", "1122334455 [1.122e9]", 5);
4256-
// test_literal(300, "testFuzz_Integer", "int32", "-777", 4);
4256+
test_literal(300, "testFuzz_Integer", "int32", "-777", 0);
42574257
test_literal(
42584258
400,
42594259
"testFuzz_Word",

0 commit comments

Comments
 (0)