Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
20 changes: 18 additions & 2 deletions crates/common/src/contracts.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
//! Commonly used contract types and functions.

use crate::compile::PathOrContractInfo;
use alloy_dyn_abi::JsonAbiExt;
use alloy_json_abi::{Event, Function, JsonAbi};
use alloy_primitives::{hex, Address, Bytes, Selector, B256};
use eyre::{OptionExt, Result};
Expand Down Expand Up @@ -123,24 +124,39 @@ impl ContractsByArtifact {

/// Finds a contract which has a similar bytecode as `code`.
pub fn find_by_creation_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
self.find_by_code(code, 0.1, ContractData::bytecode)
self.find_by_code(code, 0.1, true, ContractData::bytecode)
}

/// Finds a contract which has a similar deployed bytecode as `code`.
pub fn find_by_deployed_code(&self, code: &[u8]) -> Option<ArtifactWithContractRef<'_>> {
self.find_by_code(code, 0.15, ContractData::deployed_bytecode)
self.find_by_code(code, 0.15, false, ContractData::deployed_bytecode)
}

/// Finds a contract based on provided bytecode and accepted match score.
/// If strip constructor args flag is true then removes args from bytecode to compare.
fn find_by_code(
&self,
code: &[u8],
accepted_score: f64,
strip_ctor_args: bool,
get: impl Fn(&ContractData) -> Option<&Bytes>,
) -> Option<ArtifactWithContractRef<'_>> {
self.iter()
.filter_map(|(id, contract)| {
if let Some(deployed_bytecode) = get(contract) {
let mut code = code;
if strip_ctor_args && code.len() > deployed_bytecode.len() {
// Try to decode ctor args with contract abi.
if let Some(constructor) = contract.abi.constructor() {
let constructor_args = &code[deployed_bytecode.len()..];
if constructor.abi_decode_input(constructor_args, false).is_ok() {
// If we can decode args with current abi then remove args from
// code to compare.
code = &code[..deployed_bytecode.len()]
}
}
};

let score = bytecode_diff_score(deployed_bytecode.as_ref(), code);
(score <= accepted_score).then_some((score, (id, contract)))
} else {
Expand Down
92 changes: 92 additions & 0 deletions crates/forge/tests/cli/coverage.rs
Original file line number Diff line number Diff line change
Expand Up @@ -1753,6 +1753,98 @@ contract AContractTest is DSTest {
assert!(files.is_empty());
});

// <https://github.com/foundry-rs/foundry/issues/10172>
forgetest!(constructor_with_args, |prj, cmd| {
prj.insert_ds_test();
prj.add_source(
"ArrayCondition.sol",
r#"
contract ArrayCondition {
uint8 public constant MAX_SIZE = 32;
error TooLarge();
error EmptyArray();
// Storage variable to ensure the constructor does something
uint256 private _arrayLength;

constructor(uint256[] memory values) {
// Check for empty array
if (values.length == 0) {
revert EmptyArray();
}

if (values.length > MAX_SIZE) {
revert TooLarge();
}

// Store the array length
_arrayLength = values.length;
}

function getArrayLength() external view returns (uint256) {
return _arrayLength;
}
}
"#,
)
.unwrap();

prj.add_source(
"ArrayConditionTest.sol",
r#"
import "./test.sol";
import {ArrayCondition} from "./ArrayCondition.sol";

interface Vm {
function expectRevert(bytes4 revertData) external;
}

contract ArrayConditionTest is DSTest {
Vm constant vm = Vm(HEVM_ADDRESS);

function testValidSize() public {
uint256[] memory values = new uint256[](10);
ArrayCondition condition = new ArrayCondition(values);
assertEq(condition.getArrayLength(), 10);
}

// Test with maximum array size (should NOT revert)
function testMaxSize() public {
uint256[] memory values = new uint256[](32);
ArrayCondition condition = new ArrayCondition(values);
assertEq(condition.getArrayLength(), 32);
}

// Test with too large array size (should revert)
function testTooLarge() public {
uint256[] memory values = new uint256[](33);
vm.expectRevert(ArrayCondition.TooLarge.selector);
new ArrayCondition(values);
}

// Test with empty array (should revert)
function testEmptyArray() public {
uint256[] memory values = new uint256[](0);
vm.expectRevert(ArrayCondition.EmptyArray.selector);
new ArrayCondition(values);
}
}
"#,
)
.unwrap();

cmd.arg("coverage").assert_success().stdout_eq(str![[r#"
...
╭------------------------+---------------+---------------+---------------+---------------╮
| File | % Lines | % Statements | % Branches | % Funcs |
+========================================================================================+
| src/ArrayCondition.sol | 100.00% (8/8) | 100.00% (6/6) | 100.00% (2/2) | 100.00% (2/2) |
|------------------------+---------------+---------------+---------------+---------------|
| Total | 100.00% (8/8) | 100.00% (6/6) | 100.00% (2/2) | 100.00% (2/2) |
╰------------------------+---------------+---------------+---------------+---------------╯
...
"#]]);
});

#[track_caller]
fn assert_lcov(cmd: &mut TestCommand, data: impl IntoData) {
cmd.args(["--report=lcov", "--report-file"]).assert_file(data.into_data());
Expand Down
Loading