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
16 changes: 9 additions & 7 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down
49 changes: 44 additions & 5 deletions evm-adapters/src/sputnik/cheatcodes/cheatcode_handler.rs
Original file line number Diff line number Diff line change
Expand Up @@ -232,12 +232,16 @@ fn evm_error(retdata: &str) -> Capture<(ExitReason, Vec<u8>), 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<u8>) -> Capture<(ExitReason, Vec<u8>), Infallible> {
fn apply_cheatcode(
&mut self,
input: Vec<u8>,
transfer: Option<Transfer>,
target_gas: Option<u64>,
) -> Capture<(ExitReason, Vec<u8>), 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()),
Expand Down Expand Up @@ -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))
}

Expand Down Expand Up @@ -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);
Expand Down Expand Up @@ -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)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -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) }
Expand Down
2 changes: 2 additions & 0 deletions evm-adapters/src/sputnik/cheatcodes/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -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;
Expand Down
68 changes: 68 additions & 0 deletions evm-adapters/testdata/CheatCodes.sol
Original file line number Diff line number Diff line change
Expand Up @@ -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 {
Expand Down Expand Up @@ -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;
}
}