diff --git a/README.md b/README.md index 73c1644317e3b..f9a1642b3fe6a 100644 --- a/README.md +++ b/README.md @@ -137,13 +137,15 @@ Benchmarks TBD in the future, but: - [ ] Symbolic execution - [ ] Coverage - [ ] HEVM-style Solidity cheatcodes - - [x] roll - - [x] warp - - [x] ffi - - [x] store - - [x] load - - [ ] sign - - [ ] addr + - [x] roll: Sets block.number + - [x] warp: Sets block.timestamp + - [x] ffi: Perform foreign function call to terminal + - [x] store: Sets address storage slot + - [x] load: Loads address storage slot + - [x] deal: Sets account balance + - [x] prank: Performs a call as another address (changes msg.sender for a call) + - [x] sign: Signs data + - [x] addr: Gets address for a private key - [ ] makeEOA - ...? - [ ] Structured tracing with abi decoding diff --git a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs index b7941276eb463..f78c3ab3e7178 100644 --- a/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs +++ b/evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs @@ -232,12 +232,16 @@ fn evm_error(retdata: &str) -> Capture<(ExitReason, Vec), Infallible> { impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> { /// Decodes the provided calldata as a - fn apply_cheatcode(&mut self, input: Vec) -> Capture<(ExitReason, Vec), Infallible> { + fn apply_cheatcode( + &mut self, + input: Vec, + transfer: Option, + target_gas: Option, + ) -> Capture<(ExitReason, Vec), Infallible> { let mut res = vec![]; // Get a mutable ref to the state so we can apply the cheats let state = self.state_mut(); - let decoded = match HEVMCalls::decode(&input) { Ok(inner) => inner, Err(err) => return evm_error(&err.to_string()), @@ -330,10 +334,46 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> Token::FixedBytes(s_bytes.to_vec()), ])]); } + HEVMCalls::Prank(inner) => { + let caller = inner.0; + let address = inner.1; + let input = inner.2; + + let value = if let Some(ref transfer) = transfer { + transfer.value + } else { + U256::zero() + }; + + // change origin + let context = Context { caller, address, apparent_value: value }; + let ret = self.call( + address, + Some(Transfer { source: caller, target: address, value }), + input, + target_gas, + false, + context, + ); + res = match ret { + Capture::Exit((successful, v)) => match successful { + ExitReason::Succeed(_) => { + ethers::abi::encode(&[Token::Bool(true), Token::Bytes(v.to_vec())]) + } + _ => ethers::abi::encode(&[Token::Bool(false), Token::Bytes(v.to_vec())]), + }, + _ => vec![], + }; + } + HEVMCalls::Deal(inner) => { + let who = inner.0; + let value = inner.1; + state.reset_balance(who); + state.deposit(who, value); + } }; // TODO: Add more cheat codes. - Capture::Exit((ExitReason::Succeed(ExitSucceed::Stopped), res)) } @@ -459,7 +499,6 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> CheatcodeStackExecutor<'a, 'b, B, P> let reason = self.execute(&mut runtime); // // log::debug!(target: "evm", "Call execution using address {}: {:?}", code_address, // reason); - match reason { ExitReason::Succeed(s) => { let _ = self.handler.exit_substate(StackExitKind::Succeeded); @@ -660,7 +699,7 @@ impl<'a, 'b, B: Backend, P: PrecompileSet> Handler for CheatcodeStackExecutor<'a // NB: This is very similar to how Optimism's custom intercept logic to "predeploys" work // (e.g. with the StateManager) if code_address == *CHEATCODE_ADDRESS { - self.apply_cheatcode(input) + self.apply_cheatcode(input, transfer, target_gas) } else { self.handler.call(code_address, transfer, input, target_gas, is_static, context) } diff --git a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs index 93a4779f011ed..c285d43e62cf8 100644 --- a/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs +++ b/evm-adapters/src/sputnik/cheatcodes/memory_stackstate_owned.rs @@ -17,6 +17,12 @@ pub struct MemoryStackStateOwned<'config, B> { pub substate: MemoryStackSubstate<'config>, } +impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { + pub fn deposit(&mut self, address: H160, value: U256) { + self.substate.deposit(address, value, &self.backend); + } +} + impl<'config, B: Backend> MemoryStackStateOwned<'config, B> { pub fn new(metadata: StackSubstateMetadata<'config>, backend: B) -> Self { Self { backend, substate: MemoryStackSubstate::new(metadata) } diff --git a/evm-adapters/src/sputnik/cheatcodes/mod.rs b/evm-adapters/src/sputnik/cheatcodes/mod.rs index c47c7e5f4f791..f26604180c89a 100644 --- a/evm-adapters/src/sputnik/cheatcodes/mod.rs +++ b/evm-adapters/src/sputnik/cheatcodes/mod.rs @@ -42,6 +42,8 @@ ethers::contract::abigen!( ffi(string[])(bytes) addr(uint256)(address) sign(uint256,bytes32)(uint8,bytes32,bytes32) + prank(address,address,bytes)(bool,bytes) + deal(address,uint256) ]"#, ); pub use hevm_mod::HEVMCalls; diff --git a/evm-adapters/testdata/CheatCodes.sol b/evm-adapters/testdata/CheatCodes.sol index 458cf8830682b..a2225d70b24c4 100644 --- a/evm-adapters/testdata/CheatCodes.sol +++ b/evm-adapters/testdata/CheatCodes.sol @@ -13,6 +13,8 @@ interface Hevm { function sign(uint256,bytes32) external returns (uint8,bytes32,bytes32); function addr(uint256) external returns (address); function ffi(string[] calldata) external returns (bytes memory); + function prank(address,address,bytes calldata) external payable returns (bool, bytes memory); + function deal(address, uint256) external; } contract HasStorage { @@ -130,4 +132,70 @@ contract CheatCodes is DSTest { (string memory output) = abi.decode(res, (string)); assertEq(output, "acab"); } + + function testDeal() public { + address addr = address(1337); + hevm.deal(addr, 1337); + assertEq(addr.balance, 1337); + } + + function testPrank() public { + Prank prank = new Prank(); + address new_sender = address(1337); + bytes4 sig = prank.checksOriginAndSender.selector; + string memory input = "And his name is JOHN CENA!"; + bytes memory calld = abi.encodePacked(sig, abi.encode(input)); + address origin = tx.origin; + address sender = msg.sender; + (bool success, bytes memory ret) = hevm.prank(new_sender, address(prank), calld); + assertTrue(success); + string memory expectedRetString = "SUPER SLAM!"; + string memory actualRet = abi.decode(ret, (string)); + assertEq(actualRet, expectedRetString); + + // make sure we returned back to normal + assertEq(origin, tx.origin); + assertEq(sender, msg.sender); + } + + function testPrankValue() public { + Prank prank = new Prank(); + // setup the call + address new_sender = address(1337); + bytes4 sig = prank.checksOriginAndSender.selector; + string memory input = "And his name is JOHN CENA!"; + bytes memory calld = abi.encodePacked(sig, abi.encode(input)); + address origin = tx.origin; + address sender = msg.sender; + + // give the sender some monies + hevm.deal(new_sender, 1337); + + // call the function passing in a value. the eth is pulled from the new sender + sig = hevm.prank.selector; + calld = abi.encodePacked(sig, abi.encode(new_sender, address(prank), calld)); + + // this is nested low level calls effectively + (bool high_level_success, bytes memory outerRet) = address(hevm).call{value: 1}(calld); + assertTrue(high_level_success); + (bool success, bytes memory ret) = abi.decode(outerRet, (bool,bytes)); + assertTrue(success); + string memory expectedRetString = "SUPER SLAM!"; + string memory actualRet = abi.decode(ret, (string)); + assertEq(actualRet, expectedRetString); + + // make sure we returned back to normal + assertEq(origin, tx.origin); + assertEq(sender, msg.sender); + } +} + +contract Prank is DSTest { + function checksOriginAndSender(string calldata input) external payable returns (string memory) { + string memory expectedInput = "And his name is JOHN CENA!"; + assertEq(input, expectedInput); + assertEq(address(1337), msg.sender); + string memory expectedRetString = "SUPER SLAM!"; + return expectedRetString; + } }