Skip to content

Commit

Permalink
Add expectCallMinGas(address,uint256,uint64,bytes) cheatcode
Browse files Browse the repository at this point in the history
  • Loading branch information
clabby committed Feb 23, 2023
1 parent 7c3b3e8 commit 873a59c
Show file tree
Hide file tree
Showing 7 changed files with 59 additions and 4 deletions.
1 change: 1 addition & 0 deletions evm/src/executor/abi/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,7 @@ ethers::contract::abigen!(
expectCall(address,bytes)
expectCall(address,uint256,bytes)
expectCall(address,uint256,uint64,bytes)
expectCallMinGas(address,uint256,uint64,bytes)
getCode(string)
getDeployedCode(string)
label(address,string)
Expand Down
20 changes: 20 additions & 0 deletions evm/src/executor/inspector/cheatcodes/expect.rs
Original file line number Diff line number Diff line change
Expand Up @@ -208,6 +208,8 @@ pub struct ExpectedCallData {
pub value: Option<U256>,
/// The expected gas supplied to the call
pub gas: Option<u64>,
/// The expected *miniumum* gas supplied to the call
pub min_gas: Option<u64>,
}

#[derive(Clone, Debug, Default, PartialEq, Eq)]
Expand Down Expand Up @@ -270,6 +272,7 @@ pub fn apply<DB: DatabaseExt>(
calldata: inner.1.to_vec().into(),
value: None,
gas: None,
min_gas: None,
});
Ok(Bytes::new())
}
Expand All @@ -278,6 +281,7 @@ pub fn apply<DB: DatabaseExt>(
calldata: inner.2.to_vec().into(),
value: Some(inner.1),
gas: None,
min_gas: None,
});
Ok(Bytes::new())
}
Expand All @@ -292,6 +296,22 @@ pub fn apply<DB: DatabaseExt>(
calldata: inner.3.to_vec().into(),
value: Some(value),
gas: Some(inner.2 + positive_value_cost_stipend),
min_gas: None,
});
Ok(Bytes::new())
}
HEVMCalls::ExpectCallMinGas(inner) => {
let value = inner.1;

// If the value of the transaction is non-zero, the EVM adds a call stipend of 2300 gas
// to ensure that the basic fallback function can be called.
let positive_value_cost_stipend = if value > U256::zero() { 2300 } else { 0 };

state.expected_calls.entry(inner.0).or_default().push(ExpectedCallData {
calldata: inner.3.to_vec().into(),
value: Some(value),
gas: None,
min_gas: Some(inner.2 + positive_value_cost_stipend),
});
Ok(Bytes::new())
}
Expand Down
9 changes: 7 additions & 2 deletions evm/src/executor/inspector/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -403,7 +403,8 @@ where
expected.calldata.len() <= call.input.len() &&
expected.calldata == call.input[..expected.calldata.len()] &&
expected.value.map(|value| value == call.transfer.value).unwrap_or(true) &&
expected.gas.map(|gas| gas == call.gas_limit).unwrap_or(true)
expected.gas.map(|gas| gas == call.gas_limit).unwrap_or(true) &&
expected.min_gas.map(|min_gas| min_gas <= call.gas_limit).unwrap_or(true)
}) {
expecteds.remove(found_match);
}
Expand Down Expand Up @@ -585,11 +586,15 @@ where
Return::Revert,
remaining_gas,
format!(
"Expected a call to {:?} with data {}{}{}, but got none",
"Expected a call to {:?} with data {}{}{}{}, but got none",
address,
ethers::types::Bytes::from(expecteds[0].calldata.clone()),
expecteds[0].value.map(|v| format!(" and value {v}")).unwrap_or_default(),
expecteds[0].gas.map(|g| format!(" and gas {g}")).unwrap_or_default(),
expecteds[0]
.min_gas
.map(|g| format!(" and minimum gas {g}"))
.unwrap_or_default(),
)
.encode()
.into(),
Expand Down
2 changes: 2 additions & 0 deletions forge/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -319,6 +319,8 @@ interface Hevm {
function expectCall(address,uint256,bytes calldata) external;
// Expect a call to an address with the specified msg.value, gas, and calldata.
function expectCall(address, uint256, uint64, bytes calldata) external;
// Expect a call to an address with the specified msg.value and calldata, and a *minimum* amount of gas.
function expectCallMinGas(address, uint256, uint64, bytes calldata) external;
// Fetches the contract bytecode from its artifact file
function getCode(string calldata) external returns (bytes memory);
// Label an address in test traces
Expand Down
4 changes: 2 additions & 2 deletions testdata/cache/solidity-files-cache.json
Original file line number Diff line number Diff line change
Expand Up @@ -509,8 +509,8 @@
}
},
"cheats/ExpectCall.t.sol": {
"lastModificationDate": 1677141892508,
"contentHash": "b15f2d663c79b0e205cbf458f81540d6",
"lastModificationDate": 1677174707886,
"contentHash": "c91741f7cf3a014c0359d8a4c046bbf6",
"sourceName": "cheats/ExpectCall.t.sol",
"solcConfig": {
"settings": {
Expand Down
3 changes: 3 additions & 0 deletions testdata/cheats/Cheats.sol
Original file line number Diff line number Diff line change
Expand Up @@ -199,6 +199,9 @@ interface Cheats {
// Expect a call to an address with the specified msg.value, gas, and calldata.
function expectCall(address, uint256, uint64, bytes calldata) external;

// Expect a call to an address with the specified msg.value and calldata, and a *minimum* amount of gas.
function expectCallMinGas(address, uint256, uint64, bytes calldata) external;

// Gets the bytecode from an artifact file. Takes in the relative path to the json file
function getCode(string calldata) external returns (bytes memory);

Expand Down
24 changes: 24 additions & 0 deletions testdata/cheats/ExpectCall.t.sol
Original file line number Diff line number Diff line change
Expand Up @@ -136,4 +136,28 @@ contract ExpectCallTest is DSTest {
cheats.expectCall(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1));
target.addHardGasLimit();
}

function testExpectCallWithValueAndMinGas() public {
Contract inner = new Contract();
NestedContract target = new NestedContract(inner);

cheats.expectCallMinGas(address(inner), 1, 50_000, abi.encodeWithSelector(inner.pay.selector, 1));
target.forwardPay{value: 1}();
}

function testExpectCallWithNoValueAndMinGas() public {
Contract inner = new Contract();
NestedContract target = new NestedContract(inner);

cheats.expectCallMinGas(address(inner), 0, 25_000, abi.encodeWithSelector(inner.add.selector, 1, 1));
target.addHardGasLimit();
}

function testFailExpectCallWithNoValueAndWrongMinGas() public {
Contract inner = new Contract();
NestedContract target = new NestedContract(inner);

cheats.expectCallMinGas(address(inner), 0, 50_001, abi.encodeWithSelector(inner.add.selector, 1, 1));
target.addHardGasLimit();
}
}

0 comments on commit 873a59c

Please sign in to comment.