From 8d1dd303f9f5965df6899967acebfc020c7ef0e5 Mon Sep 17 00:00:00 2001 From: pistomat Date: Fri, 4 Aug 2023 15:38:15 +0200 Subject: [PATCH] feat(forge): Implement vm.Sleep (#5519) * implement vm sleep * forge fmt * remove println --- abi/abi/HEVM.sol | 2 + abi/src/bindings/hevm.rs | 52 ++++++++++++++++++++ evm/src/executor/inspector/cheatcodes/ext.rs | 9 ++++ testdata/cheats/Sleep.t.sol | 51 +++++++++++++++++++ testdata/cheats/Vm.sol | 3 ++ 5 files changed, 117 insertions(+) create mode 100644 testdata/cheats/Sleep.t.sol diff --git a/abi/abi/HEVM.sol b/abi/abi/HEVM.sol index 74f16b076..2c7094f57 100644 --- a/abi/abi/HEVM.sol +++ b/abi/abi/HEVM.sol @@ -217,3 +217,5 @@ stopMappingRecording() getMappingLength(address,bytes32) getMappingSlotAt(address,bytes32,uint256) getMappingKeyAndParentOf(address,bytes32) + +sleep(uint256) diff --git a/abi/src/bindings/hevm.rs b/abi/src/bindings/hevm.rs index b4d64d74f..1a82d3bbf 100644 --- a/abi/src/bindings/hevm.rs +++ b/abi/src/bindings/hevm.rs @@ -4339,6 +4339,24 @@ pub mod hevm { }, ], ), + ( + ::std::borrow::ToOwned::to_owned("sleep"), + ::std::vec![ + ::ethers_core::abi::ethabi::Function { + name: ::std::borrow::ToOwned::to_owned("sleep"), + inputs: ::std::vec![ + ::ethers_core::abi::ethabi::Param { + name: ::std::string::String::new(), + kind: ::ethers_core::abi::ethabi::ParamType::Uint(256usize), + internal_type: ::core::option::Option::None, + }, + ], + outputs: ::std::vec![], + constant: ::core::option::Option::None, + state_mutability: ::ethers_core::abi::ethabi::StateMutability::NonPayable, + }, + ], + ), ( ::std::borrow::ToOwned::to_owned("snapshot"), ::std::vec![ @@ -6604,6 +6622,15 @@ pub mod hevm { .method_hash([221, 130, 209, 62], p0) .expect("method not found (this should never happen)") } + ///Calls the contract's `sleep` (0xfa9d8713) function + pub fn sleep( + &self, + p0: ::ethers_core::types::U256, + ) -> ::ethers_contract::builders::ContractCall { + self.0 + .method_hash([250, 157, 135, 19], p0) + .expect("method not found (this should never happen)") + } ///Calls the contract's `snapshot` (0x9711715a) function pub fn snapshot( &self, @@ -9317,6 +9344,19 @@ pub mod hevm { )] #[ethcall(name = "skip", abi = "skip(bool)")] pub struct SkipCall(pub bool); + ///Container type for all input parameters for the `sleep` function with signature `sleep(uint256)` and selector `0xfa9d8713` + #[derive( + Clone, + ::ethers_contract::EthCall, + ::ethers_contract::EthDisplay, + Default, + Debug, + PartialEq, + Eq, + Hash + )] + #[ethcall(name = "sleep", abi = "sleep(uint256)")] + pub struct SleepCall(pub ::ethers_core::types::U256); ///Container type for all input parameters for the `snapshot` function with signature `snapshot()` and selector `0x9711715a` #[derive( Clone, @@ -9838,6 +9878,7 @@ pub mod hevm { SetNonceUnsafe(SetNonceUnsafeCall), Sign(SignCall), Skip(SkipCall), + Sleep(SleepCall), Snapshot(SnapshotCall), StartBroadcast0(StartBroadcast0Call), StartBroadcast1(StartBroadcast1Call), @@ -10592,6 +10633,10 @@ pub mod hevm { = ::decode(data) { return Ok(Self::Skip(decoded)); } + if let Ok(decoded) + = ::decode(data) { + return Ok(Self::Sleep(decoded)); + } if let Ok(decoded) = ::decode(data) { return Ok(Self::Snapshot(decoded)); @@ -11110,6 +11155,7 @@ pub mod hevm { } Self::Sign(element) => ::ethers_core::abi::AbiEncode::encode(element), Self::Skip(element) => ::ethers_core::abi::AbiEncode::encode(element), + Self::Sleep(element) => ::ethers_core::abi::AbiEncode::encode(element), Self::Snapshot(element) => ::ethers_core::abi::AbiEncode::encode(element), Self::StartBroadcast0(element) => { ::ethers_core::abi::AbiEncode::encode(element) @@ -11374,6 +11420,7 @@ pub mod hevm { Self::SetNonceUnsafe(element) => ::core::fmt::Display::fmt(element, f), Self::Sign(element) => ::core::fmt::Display::fmt(element, f), Self::Skip(element) => ::core::fmt::Display::fmt(element, f), + Self::Sleep(element) => ::core::fmt::Display::fmt(element, f), Self::Snapshot(element) => ::core::fmt::Display::fmt(element, f), Self::StartBroadcast0(element) => ::core::fmt::Display::fmt(element, f), Self::StartBroadcast1(element) => ::core::fmt::Display::fmt(element, f), @@ -12257,6 +12304,11 @@ pub mod hevm { Self::Skip(value) } } + impl ::core::convert::From for HEVMCalls { + fn from(value: SleepCall) -> Self { + Self::Sleep(value) + } + } impl ::core::convert::From for HEVMCalls { fn from(value: SnapshotCall) -> Self { Self::Snapshot(value) diff --git a/evm/src/executor/inspector/cheatcodes/ext.rs b/evm/src/executor/inspector/cheatcodes/ext.rs index d4871cf01..24e228b39 100644 --- a/evm/src/executor/inspector/cheatcodes/ext.rs +++ b/evm/src/executor/inspector/cheatcodes/ext.rs @@ -418,6 +418,14 @@ fn key_exists(json_str: &str, key: &str) -> Result { Ok(exists) } +/// Sleeps for a given amount of milliseconds. +fn sleep(milliseconds: &U256) -> Result { + let sleep_duration = std::time::Duration::from_millis(milliseconds.as_u64()); + std::thread::sleep(sleep_duration); + + Ok(Default::default()) +} + #[instrument(level = "error", name = "ext", target = "evm::cheatcodes", skip_all)] pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { Some(match call { @@ -592,6 +600,7 @@ pub fn apply(state: &mut Cheatcodes, call: &HEVMCalls) -> Option { HEVMCalls::SerializeBytes1(inner) => { serialize_json(state, &inner.0, &inner.1, &array_str_to_str(&inner.2)) } + HEVMCalls::Sleep(inner) => sleep(&inner.0), HEVMCalls::WriteJson0(inner) => write_json(state, &inner.0, &inner.1, None), HEVMCalls::WriteJson1(inner) => write_json(state, &inner.0, &inner.1, Some(&inner.2)), HEVMCalls::KeyExists(inner) => key_exists(&inner.0, &inner.1), diff --git a/testdata/cheats/Sleep.t.sol b/testdata/cheats/Sleep.t.sol new file mode 100644 index 000000000..3efc8b127 --- /dev/null +++ b/testdata/cheats/Sleep.t.sol @@ -0,0 +1,51 @@ +// SPDX-License-Identifier: Unlicense +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "./Vm.sol"; + +contract SleepTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + function testSleep() public { + uint256 milliseconds = 1234; + + string[] memory inputs = new string[](2); + inputs[0] = "date"; + // OS X does not support precision more than 1 second + inputs[1] = "+%s000"; + + bytes memory res = vm.ffi(inputs); + uint256 start = vm.parseUint(string(res)); + + vm.sleep(milliseconds); + + res = vm.ffi(inputs); + uint256 end = vm.parseUint(string(res)); + + // Limit precision to 1000 ms + assertGe(end - start, milliseconds / 1000 * 1000, "sleep failed"); + } + + /// forge-config: default.fuzz.runs = 10 + function testSleepFuzzed(uint256 _milliseconds) public { + // Limit sleep time to 2 seconds to decrease test time + uint256 milliseconds = _milliseconds % 2000; + + string[] memory inputs = new string[](2); + inputs[0] = "date"; + // OS X does not support precision more than 1 second + inputs[1] = "+%s000"; + + bytes memory res = vm.ffi(inputs); + uint256 start = vm.parseUint(string(res)); + + vm.sleep(milliseconds); + + res = vm.ffi(inputs); + uint256 end = vm.parseUint(string(res)); + + // Limit precision to 1000 ms + assertGe(end - start, milliseconds / 1000 * 1000, "sleep failed"); + } +} diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index 9a176ca6f..555d21fb0 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -183,6 +183,9 @@ interface Vm { // Skips a test. function skip(bool) external; + // Sleeps for a given number of milliseconds. + function sleep(uint256) external; + // Expects an error on next call function expectRevert() external;