diff --git a/.gitignore b/.gitignore index 19f666e451bf9..5b61e32022994 100644 --- a/.gitignore +++ b/.gitignore @@ -1,6 +1,7 @@ .DS_STORE /target out/ +snapshots/ out.json .idea .vscode diff --git a/Cargo.lock b/Cargo.lock index cc0bdae612347..a4426217470da 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -89,28 +89,44 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "629b62e38d471cc15fea534eb7283d2f8a4e8bdb1811bcc5d66dda6cfce6fae1" dependencies = [ - "alloy-eips", + "alloy-eips 0.3.6", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.3.6", "c-kzg", "serde", ] +[[package]] +name = "alloy-consensus" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "705687d5bfd019fee57cf9e206b27b30a9a9617535d5590a02b171e813208f8e" +dependencies = [ + "alloy-eips 0.4.2", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 0.4.2", + "auto_impl", + "c-kzg", + "derive_more 1.0.0", + "serde", +] + [[package]] name = "alloy-contract" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0eefe64fd344cffa9cf9e3435ec4e93e6e9c3481bc37269af988bf497faf4a6a" +checksum = "917f7d12cf3971dc8c11c9972f732b35ccb9aaaf5f28f2f87e9e6523bee3a8ad" dependencies = [ "alloy-dyn-abi", "alloy-json-abi", "alloy-network", - "alloy-network-primitives", + "alloy-network-primitives 0.4.0", "alloy-primitives", "alloy-provider", "alloy-pubsub", - "alloy-rpc-types-eth", + "alloy-rpc-types-eth 0.4.0", "alloy-sol-types", "alloy-transport", "futures", @@ -154,9 +170,9 @@ dependencies = [ [[package]] name = "alloy-eip7702" -version = "0.1.0" +version = "0.1.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "37d319bb544ca6caeab58c39cea8921c55d924d4f68f2c60f24f914673f9a74a" +checksum = "ea59dc42102bc9a1905dc57901edc6dd48b9f38115df86c7d252acba70d71d04" dependencies = [ "alloy-primitives", "alloy-rlp", @@ -176,7 +192,25 @@ dependencies = [ "alloy-eip7702", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.3.6", + "c-kzg", + "derive_more 1.0.0", + "once_cell", + "serde", + "sha2", +] + +[[package]] +name = "alloy-eips" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6ffb906284a1e1f63c4607da2068c8197458a352d0b3e9796e67353d72a9be85" +dependencies = [ + "alloy-eip2930", + "alloy-eip7702", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 0.4.2", "c-kzg", "derive_more 1.0.0", "once_cell", @@ -186,12 +220,12 @@ dependencies = [ [[package]] name = "alloy-genesis" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "3a7a18afb0b318616b6b2b0e2e7ac5529d32a966c673b48091c9919e284e6aca" +checksum = "8429cf4554eed9b40feec7f4451113e76596086447550275e3def933faf47ce3" dependencies = [ "alloy-primitives", - "alloy-serde", + "alloy-serde 0.4.2", "serde", ] @@ -209,9 +243,9 @@ dependencies = [ [[package]] name = "alloy-json-rpc" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d3c717b5298fad078cd3a418335b266eba91b511383ca9bd497f742d5975d5ab" +checksum = "f8fa8a1a3c4cbd221f2b8e3693aeb328fca79a757fe556ed08e47bbbc2a70db7" dependencies = [ "alloy-primitives", "alloy-sol-types", @@ -223,17 +257,17 @@ dependencies = [ [[package]] name = "alloy-network" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "fb3705ce7d8602132bcf5ac7a1dd293a42adc2f183abf5907c30ac535ceca049" +checksum = "85fa23a6a9d612b52e402c995f2d582c25165ec03ac6edf64c861a76bc5b87cd" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 0.4.2", + "alloy-eips 0.4.2", "alloy-json-rpc", - "alloy-network-primitives", + "alloy-network-primitives 0.4.0", "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-serde", + "alloy-rpc-types-eth 0.4.0", + "alloy-serde 0.4.2", "alloy-signer", "alloy-sol-types", "async-trait", @@ -248,9 +282,22 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94ad40869867ed2d9cd3842b1e800889e5b49e6b92da346e93862b4a741bedf3" dependencies = [ - "alloy-eips", + "alloy-eips 0.3.6", + "alloy-primitives", + "alloy-serde 0.3.6", + "serde", +] + +[[package]] +name = "alloy-network-primitives" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8416e4e9ceee8014d2f89fc3dde331da392b26d14226a0d5cbc207ae7799fb2f" +dependencies = [ + "alloy-consensus 0.4.2", + "alloy-eips 0.4.2", "alloy-primitives", - "alloy-serde", + "alloy-serde 0.4.2", "serde", ] @@ -287,20 +334,20 @@ dependencies = [ [[package]] name = "alloy-provider" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "927f708dd457ed63420400ee5f06945df9632d5d101851952056840426a10dc5" +checksum = "fcfaa4ffec0af04e3555686b8aacbcdf7d13638133a0672749209069750f78a6" dependencies = [ "alloy-chains", - "alloy-consensus", - "alloy-eips", + "alloy-consensus 0.4.2", + "alloy-eips 0.4.2", "alloy-json-rpc", "alloy-network", - "alloy-network-primitives", + "alloy-network-primitives 0.4.0", "alloy-primitives", "alloy-pubsub", "alloy-rpc-client", - "alloy-rpc-types-eth", + "alloy-rpc-types-eth 0.4.0", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", "alloy-transport", @@ -326,9 +373,9 @@ dependencies = [ [[package]] name = "alloy-pubsub" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2d05f63677e210d758cd5d6d1ce10f20c980c3560ccfbe79ba1997791862a04f" +checksum = "f32cef487122ae75c91eb50154c70801d71fabdb976fec6c49e0af5e6486ab15" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -367,9 +414,9 @@ dependencies = [ [[package]] name = "alloy-rpc-client" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7d82952dca71173813d4e5733e2c986d8b04aea9e0f3b0a576664c232ad050a5" +checksum = "370143ed581aace6e663342d21d209c6b2e34ee6142f7d6675adb518deeaf0dc" dependencies = [ "alloy-json-rpc", "alloy-primitives", @@ -392,45 +439,47 @@ dependencies = [ [[package]] name = "alloy-rpc-types" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "64333d639f2a0cf73491813c629a405744e16343a4bc5640931be707c345ecc5" +checksum = "9ffc534b7919e18f35e3aa1f507b6f3d9d92ec298463a9f6beaac112809d8d06" dependencies = [ + "alloy-primitives", "alloy-rpc-types-anvil", "alloy-rpc-types-engine", - "alloy-rpc-types-eth", + "alloy-rpc-types-eth 0.4.0", "alloy-rpc-types-trace", "alloy-rpc-types-txpool", - "alloy-serde", + "alloy-serde 0.4.2", "serde", ] [[package]] name = "alloy-rpc-types-anvil" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "d25cb45ad7c0930dd62eecf164d2afe4c3d2dd2c82af85680ad1f118e1e5cb83" +checksum = "142f6fb21ef1857b3d175dc16b73d67f4b70914e6898610da3c0b65a1281fe7b" dependencies = [ "alloy-primitives", - "alloy-serde", + "alloy-serde 0.4.2", "serde", ] [[package]] name = "alloy-rpc-types-engine" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1464c4dd646e1bdfde86ae65ce5ba168dbb29180b478011fe87117ae46b1629b" +checksum = "c032e9b725a990be03cc0ddd9fa73c21f61d1449b328083aa22fbfafb03eda1b" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 0.4.2", + "alloy-eips 0.4.2", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.4.2", "derive_more 1.0.0", "jsonwebtoken", "rand", "serde", + "strum", ] [[package]] @@ -439,12 +488,12 @@ version = "0.3.6" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "83aa984386deda02482660aa31cb8ca1e63d533f1c31a52d7d181ac5ec68e9b8" dependencies = [ - "alloy-consensus", - "alloy-eips", - "alloy-network-primitives", + "alloy-consensus 0.3.6", + "alloy-eips 0.3.6", + "alloy-network-primitives 0.3.6", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.3.6", "alloy-sol-types", "cfg-if", "derive_more 1.0.0", @@ -454,15 +503,34 @@ dependencies = [ "serde_json", ] +[[package]] +name = "alloy-rpc-types-eth" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3e1f655dcd5e9ccf215cbffb69272698ef6b3ec76907e8937345f2a82ae04ed4" +dependencies = [ + "alloy-consensus 0.4.2", + "alloy-eips 0.4.2", + "alloy-network-primitives 0.4.0", + "alloy-primitives", + "alloy-rlp", + "alloy-serde 0.4.2", + "alloy-sol-types", + "derive_more 1.0.0", + "itertools 0.13.0", + "serde", + "serde_json", +] + [[package]] name = "alloy-rpc-types-trace" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "98db35cd42c90b484377e6bc44d95377a7a38a5ebee996e67754ac0446d542ab" +checksum = "6900c7d94528217465f6b619f03adb2eecc9682f9083d49ad7d40ec6eda0ed04" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-serde", + "alloy-rpc-types-eth 0.4.0", + "alloy-serde 0.4.2", "serde", "serde_json", "thiserror", @@ -470,13 +538,13 @@ dependencies = [ [[package]] name = "alloy-rpc-types-txpool" -version = "0.3.6" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6bac37082c3b21283b3faf5cc0e08974272aee2f756ce1adeb26db56a5fce0d5" +checksum = "954781be5ca2e15db08d753712f494504a04771ee4296de1e834e65c105b8ec3" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-serde", + "alloy-rpc-types-eth 0.4.0", + "alloy-serde 0.4.2", "serde", ] @@ -491,11 +559,22 @@ dependencies = [ "serde_json", ] +[[package]] +name = "alloy-serde" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9dff0ab1cdd43ca001e324dc27ee0e8606bd2161d6623c63e0e0b8c4dfc13600" +dependencies = [ + "alloy-primitives", + "serde", + "serde_json", +] + [[package]] name = "alloy-signer" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "307324cca94354cd654d6713629f0383ec037e1ff9e3e3d547212471209860c0" +checksum = "2fd4e0ad79c81a27ca659be5d176ca12399141659fef2bcbfdc848da478f4504" dependencies = [ "alloy-dyn-abi", "alloy-primitives", @@ -509,11 +588,11 @@ dependencies = [ [[package]] name = "alloy-signer-aws" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "076be69aa26a4c500919f1ad3847662aa6d1e9bc2995e263ed826b1546d1b990" +checksum = "417e19d9844350d11f7426d4920a5df777f8c2abbb7a70d9de6f1faf284db15b" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-network", "alloy-primitives", "alloy-signer", @@ -527,11 +606,11 @@ dependencies = [ [[package]] name = "alloy-signer-gcp" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cabd79d4eb954a8c2ae7889a18e2466af186ae68376251cf58525239c156ec54" +checksum = "b6fd12ae28e8330766821058ed9a6a06ae4f9b12c776e8a7cfb16e3a954f508e" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-network", "alloy-primitives", "alloy-signer", @@ -545,11 +624,11 @@ dependencies = [ [[package]] name = "alloy-signer-ledger" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f3df66f5ddcc32d2070485dc702f5f5fb97cfbfa817f6e2e6bac16a4e32ed44c" +checksum = "d3a02400dea370022151f07581b73a836115c88ce4df350206653493bec024e2" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-dyn-abi", "alloy-network", "alloy-primitives", @@ -565,18 +644,17 @@ dependencies = [ [[package]] name = "alloy-signer-local" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9fabe917ab1778e760b4701628d1cae8e028ee9d52ac6307de4e1e9286ab6b5f" +checksum = "494e0a256f3e99f2426f994bcd1be312c02cb8f88260088dacb33a8b8936475f" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-network", "alloy-primitives", "alloy-signer", "async-trait", "coins-bip32", "coins-bip39", - "elliptic-curve", "eth-keystore", "k256", "rand", @@ -585,11 +663,11 @@ dependencies = [ [[package]] name = "alloy-signer-trezor" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "1068949eda889b2c052b29a6e8c7ea2ba16be6d1af83ad165fff2a4e4ad19fcd" +checksum = "e0d91ddee2390b002148128e47902893deba353eb1b818a3afb6c960ebf4e42c" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-network", "alloy-primitives", "alloy-signer", @@ -675,9 +753,9 @@ dependencies = [ [[package]] name = "alloy-transport" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "33616b2edf7454302a1d48084db185e52c309f73f6c10be99b0fe39354b3f1e9" +checksum = "2ac3e97dad3d31770db0fc89bd6a63b789fbae78963086733f960cf32c483904" dependencies = [ "alloy-json-rpc", "base64 0.22.1", @@ -694,9 +772,9 @@ dependencies = [ [[package]] name = "alloy-transport-http" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a944f5310c690b62bbb3e7e5ce34527cbd36b2d18532a797af123271ce595a49" +checksum = "b367dcccada5b28987c2296717ee04b9a5637aacd78eacb1726ef211678b5212" dependencies = [ "alloy-json-rpc", "alloy-transport", @@ -709,9 +787,9 @@ dependencies = [ [[package]] name = "alloy-transport-ipc" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "09fd8491249f74d16ec979b1f5672377b12ebb818e6056478ffa386954dbd350" +checksum = "b90cf9cde7f2fce617da52768ee28f522264b282d148384a4ca0ea85af04fa3a" dependencies = [ "alloy-json-rpc", "alloy-pubsub", @@ -730,9 +808,9 @@ dependencies = [ [[package]] name = "alloy-transport-ws" -version = "0.3.6" +version = "0.4.2" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "a9704761f6297fe482276bee7f77a93cb42bd541c2bd6c1c560b6f3a9ece672e" +checksum = "7153b88690de6a50bba81c11e1d706bc41dbb90126d607404d60b763f6a3947f" dependencies = [ "alloy-pubsub", "alloy-transport", @@ -741,7 +819,7 @@ dependencies = [ "rustls 0.23.13", "serde_json", "tokio", - "tokio-tungstenite 0.23.1", + "tokio-tungstenite", "tracing", "ws_stream_wasm", ] @@ -849,10 +927,10 @@ name = "anvil" version = "0.2.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-contract", "alloy-dyn-abi", - "alloy-eips", + "alloy-eips 0.4.2", "alloy-genesis", "alloy-json-abi", "alloy-json-rpc", @@ -863,7 +941,7 @@ dependencies = [ "alloy-rlp", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -917,13 +995,13 @@ dependencies = [ name = "anvil-core" version = "0.2.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-dyn-abi", - "alloy-eips", + "alloy-eips 0.4.2", "alloy-primitives", "alloy-rlp", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-trie", "bytes", "foundry-common", @@ -1601,7 +1679,7 @@ dependencies = [ "sha1", "sync_wrapper 1.0.1", "tokio", - "tokio-tungstenite 0.24.0", + "tokio-tungstenite", "tower 0.5.1", "tower-layer", "tower-service", @@ -1916,9 +1994,9 @@ dependencies = [ [[package]] name = "cc" -version = "1.1.22" +version = "1.1.24" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "9540e661f81799159abee814118cc139a2004b3a3aa3ea37724a1b66530b90e0" +checksum = "812acba72f0a070b003d3697490d2b55b837230ae7c6c6497f05cc2ddbb8d938" dependencies = [ "shlex", ] @@ -3247,14 +3325,14 @@ name = "forge" version = "0.2.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-dyn-abi", "alloy-json-abi", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-signer", "alloy-signer-local", "alloy-sol-macro-expander", @@ -3377,15 +3455,15 @@ name = "forge-script" version = "0.2.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-dyn-abi", - "alloy-eips", + "alloy-eips 0.4.2", "alloy-json-abi", "alloy-network", "alloy-primitives", "alloy-provider", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-signer", "alloy-transport", "async-recursion", @@ -3498,7 +3576,7 @@ name = "foundry-cast" version = "0.2.0" dependencies = [ "alloy-chains", - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", @@ -3508,7 +3586,7 @@ dependencies = [ "alloy-provider", "alloy-rlp", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-signer", "alloy-signer-local", "alloy-sol-types", @@ -3556,7 +3634,7 @@ dependencies = [ name = "foundry-cheatcodes" version = "0.2.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-dyn-abi", "alloy-genesis", "alloy-json-abi", @@ -3649,7 +3727,7 @@ dependencies = [ name = "foundry-common" version = "0.2.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-contract", "alloy-dyn-abi", "alloy-json-abi", @@ -3659,7 +3737,7 @@ dependencies = [ "alloy-pubsub", "alloy-rpc-client", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-sol-types", "alloy-transport", "alloy-transport-http", @@ -3695,11 +3773,11 @@ dependencies = [ name = "foundry-common-fmt" version = "0.2.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-dyn-abi", "alloy-primitives", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "chrono", "comfy-table", "foundry-macros", @@ -3923,7 +4001,7 @@ dependencies = [ "alloy-primitives", "alloy-provider", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-sol-types", "alloy-transport", "auto_impl", @@ -4019,14 +4097,14 @@ dependencies = [ [[package]] name = "foundry-fork-db" -version = "0.3.2" +version = "0.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "2fb76083f203e341deeb4a03f9cc86df096f09c723c42e44c25052c233ed87b4" +checksum = "2c8bdd63ecf8309c549d41a6167510da5053b13e9ab5456f06aff1070c0b642f" dependencies = [ "alloy-primitives", "alloy-provider", "alloy-rpc-types", - "alloy-serde", + "alloy-serde 0.4.2", "alloy-transport", "eyre", "futures", @@ -4086,7 +4164,7 @@ dependencies = [ name = "foundry-wallets" version = "0.2.0" dependencies = [ - "alloy-consensus", + "alloy-consensus 0.4.2", "alloy-dyn-abi", "alloy-network", "alloy-primitives", @@ -4799,9 +4877,9 @@ checksum = "08a397c49fec283e3d6211adbe480be95aae5f304cfb923e9970e08956d5168a" [[package]] name = "httparse" -version = "1.9.4" +version = "1.9.5" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "0fcc0b4a115bf80b728eb8ea024ad5bd707b615bfed49e0665b6e0f86fd082d9" +checksum = "7d71d3574edd2771538b901e6549113b4006ece66150fb69c0fb6d9a2adae946" [[package]] name = "httpdate" @@ -6015,11 +6093,11 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "21aad1fbf80d2bcd7406880efc7ba109365f44bbb72896758ddcbfa46bf1592c" dependencies = [ - "alloy-consensus", - "alloy-eips", + "alloy-consensus 0.3.6", + "alloy-eips 0.3.6", "alloy-primitives", "alloy-rlp", - "alloy-serde", + "alloy-serde 0.3.6", "derive_more 1.0.0", "serde", "spin", @@ -6031,11 +6109,11 @@ version = "0.2.12" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e281fbfc2198b7c0c16457d6524f83d192662bc9f3df70f24c3038d4521616df" dependencies = [ - "alloy-eips", - "alloy-network-primitives", + "alloy-eips 0.3.6", + "alloy-network-primitives 0.3.6", "alloy-primitives", - "alloy-rpc-types-eth", - "alloy-serde", + "alloy-rpc-types-eth 0.3.6", + "alloy-serde 0.3.6", "cfg-if", "hashbrown 0.14.5", "op-alloy-consensus", @@ -7101,9 +7179,9 @@ checksum = "2b15c43186be67a4fd63bee50d0303afffcef381492ebe2c5d87f324e1b8815c" [[package]] name = "reqwest" -version = "0.12.7" +version = "0.12.8" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "f8f4955649ef5c38cc7f9e8aa41761d48fb9677197daea9984dc54f56aad5e63" +checksum = "f713147fbe92361e52392c73b8c9e48c04c6625bce969ef54dc901e58e042a7b" dependencies = [ "async-compression", "base64 0.22.1", @@ -7129,8 +7207,8 @@ dependencies = [ "pin-project-lite", "quinn", "rustls 0.23.13", - "rustls-native-certs 0.7.3", - "rustls-pemfile 2.1.3", + "rustls-native-certs 0.8.0", + "rustls-pemfile 2.2.0", "rustls-pki-types", "serde", "serde_json", @@ -7168,12 +7246,12 @@ dependencies = [ [[package]] name = "revm-inspectors" -version = "0.7.7" +version = "0.8.1" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "cd8e3bae0d5c824da0ac883e2521c5e83870d6521eeeccd4ee54266aa3cc1a51" +checksum = "43c44af0bf801f48d25f7baf25cf72aff4c02d610f83b428175228162fef0246" dependencies = [ "alloy-primitives", - "alloy-rpc-types-eth", + "alloy-rpc-types-eth 0.4.0", "alloy-rpc-types-trace", "alloy-sol-types", "anstyle", @@ -7452,19 +7530,6 @@ dependencies = [ "security-framework", ] -[[package]] -name = "rustls-native-certs" -version = "0.7.3" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "e5bfb394eeed242e909609f56089eecfe5fda225042e8b171791b9c95f5931e5" -dependencies = [ - "openssl-probe", - "rustls-pemfile 2.1.3", - "rustls-pki-types", - "schannel", - "security-framework", -] - [[package]] name = "rustls-native-certs" version = "0.8.0" @@ -7472,7 +7537,7 @@ source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "fcaf18a4f2be7326cd874a5fa579fae794320a0f388d365dca7e480e55f83f8a" dependencies = [ "openssl-probe", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "rustls-pki-types", "schannel", "security-framework", @@ -7489,11 +7554,10 @@ dependencies = [ [[package]] name = "rustls-pemfile" -version = "2.1.3" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "196fe16b00e106300d3e45ecfcb764fa292a535d7326a29a5875c579c7417425" +checksum = "dce314e5fee3f39953d46bb63bb8a46d40c2f8fb7cc5a3b6cab2bde9721d6e50" dependencies = [ - "base64 0.22.1", "rustls-pki-types", ] @@ -7624,9 +7688,9 @@ dependencies = [ [[package]] name = "scc" -version = "2.1.18" +version = "2.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "215b1103f73e23e9cb6883072c1fb26ae55c09d42054654955c739e5418a7c96" +checksum = "836f1e0f4963ef5288b539b643b35e043e76a32d0f4e47e67febf69576527f50" dependencies = [ "sdd", ] @@ -8652,9 +8716,9 @@ dependencies = [ [[package]] name = "tokio-tungstenite" -version = "0.23.1" +version = "0.24.0" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "c6989540ced10490aaf14e6bad2e3d33728a2813310a0c71d1574304c49631cd" +checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" dependencies = [ "futures-util", "log", @@ -8662,22 +8726,10 @@ dependencies = [ "rustls-pki-types", "tokio", "tokio-rustls 0.26.0", - "tungstenite 0.23.0", + "tungstenite", "webpki-roots", ] -[[package]] -name = "tokio-tungstenite" -version = "0.24.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "edc5f74e248dc973e0dbb7b74c7e0d6fcc301c694ff50049504004ef4d0cdcd9" -dependencies = [ - "futures-util", - "log", - "tokio", - "tungstenite 0.24.0", -] - [[package]] name = "tokio-util" version = "0.7.12" @@ -8757,7 +8809,7 @@ dependencies = [ "pin-project 1.1.5", "prost", "rustls-native-certs 0.8.0", - "rustls-pemfile 2.1.3", + "rustls-pemfile 2.2.0", "socket2", "tokio", "tokio-rustls 0.26.0", @@ -8982,26 +9034,6 @@ version = "0.2.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e421abadd41a4225275504ea4d6566923418b7f05506fbc9c0fe86ba7396114b" -[[package]] -name = "tungstenite" -version = "0.23.0" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "6e2e2ce1e47ed2994fd43b04c8f618008d4cabdd5ee34027cf14f9d918edd9c8" -dependencies = [ - "byteorder", - "bytes", - "data-encoding", - "http 1.1.0", - "httparse", - "log", - "rand", - "rustls 0.23.13", - "rustls-pki-types", - "sha1", - "thiserror", - "utf-8", -] - [[package]] name = "tungstenite" version = "0.24.0" @@ -9015,6 +9047,8 @@ dependencies = [ "httparse", "log", "rand", + "rustls 0.23.13", + "rustls-pki-types", "sha1", "thiserror", "utf-8", diff --git a/Cargo.toml b/Cargo.toml index ae6ee18250fa7..bdc2c87722340 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -168,39 +168,39 @@ foundry-linking = { path = "crates/linking" } # solc & compilation utilities foundry-block-explorers = { version = "0.7.3", default-features = false } foundry-compilers = { version = "0.11.3", default-features = false } -foundry-fork-db = "0.3.2" +foundry-fork-db = "0.4.0" solang-parser = "=0.3.3" ## revm revm = { version = "14.0.3", default-features = false } revm-primitives = { version = "10.0.0", default-features = false } -revm-inspectors = { version = "0.7.7", features = ["serde"] } +revm-inspectors = { version = "0.8.0", features = ["serde"] } ## ethers ethers-contract-abigen = { version = "2.0.14", default-features = false } ## alloy -alloy-consensus = { version = "0.3.6", default-features = false } -alloy-contract = { version = "0.3.6", default-features = false } -alloy-eips = { version = "0.3.6", default-features = false } -alloy-genesis = { version = "0.3.6", default-features = false } -alloy-json-rpc = { version = "0.3.6", default-features = false } -alloy-network = { version = "0.3.6", default-features = false } -alloy-provider = { version = "0.3.6", default-features = false } -alloy-pubsub = { version = "0.3.6", default-features = false } -alloy-rpc-client = { version = "0.3.6", default-features = false } -alloy-rpc-types = { version = "0.3.6", default-features = true } -alloy-serde = { version = "0.3.6", default-features = false } -alloy-signer = { version = "0.3.6", default-features = false } -alloy-signer-aws = { version = "0.3.6", default-features = false } -alloy-signer-gcp = { version = "0.3.6", default-features = false } -alloy-signer-ledger = { version = "0.3.6", default-features = false } -alloy-signer-local = { version = "0.3.6", default-features = false } -alloy-signer-trezor = { version = "0.3.6", default-features = false } -alloy-transport = { version = "0.3.6", default-features = false } -alloy-transport-http = { version = "0.3.6", default-features = false } -alloy-transport-ipc = { version = "0.3.6", default-features = false } -alloy-transport-ws = { version = "0.3.6", default-features = false } +alloy-consensus = { version = "0.4.2", default-features = false } +alloy-contract = { version = "0.4.2", default-features = false } +alloy-eips = { version = "0.4.2", default-features = false } +alloy-genesis = { version = "0.4.2", default-features = false } +alloy-json-rpc = { version = "0.4.2", default-features = false } +alloy-network = { version = "0.4.2", default-features = false } +alloy-provider = { version = "0.4.2", default-features = false } +alloy-pubsub = { version = "0.4.2", default-features = false } +alloy-rpc-client = { version = "0.4.2", default-features = false } +alloy-rpc-types = { version = "0.4.2", default-features = true } +alloy-serde = { version = "0.4.2", default-features = false } +alloy-signer = { version = "0.4.2", default-features = false } +alloy-signer-aws = { version = "0.4.2", default-features = false } +alloy-signer-gcp = { version = "0.4.2", default-features = false } +alloy-signer-ledger = { version = "0.4.2", default-features = false } +alloy-signer-local = { version = "0.4.2", default-features = false } +alloy-signer-trezor = { version = "0.4.2", default-features = false } +alloy-transport = { version = "0.4.2", default-features = false } +alloy-transport-http = { version = "0.4.2", default-features = false } +alloy-transport-ipc = { version = "0.4.2", default-features = false } +alloy-transport-ws = { version = "0.4.2", default-features = false } ## alloy-core alloy-dyn-abi = "0.8.5" diff --git a/crates/anvil/core/src/eth/block.rs b/crates/anvil/core/src/eth/block.rs index 1b0895dcd5f57..337c5cfd8a57f 100644 --- a/crates/anvil/core/src/eth/block.rs +++ b/crates/anvil/core/src/eth/block.rs @@ -90,16 +90,16 @@ pub struct PartialHeader { pub logs_bloom: Bloom, pub difficulty: U256, pub number: u64, - pub gas_limit: u128, - pub gas_used: u128, + pub gas_limit: u64, + pub gas_used: u64, pub timestamp: u64, pub extra_data: Bytes, pub mix_hash: B256, - pub blob_gas_used: Option<u128>, - pub excess_blob_gas: Option<u128>, + pub blob_gas_used: Option<u64>, + pub excess_blob_gas: Option<u64>, pub parent_beacon_block_root: Option<B256>, pub nonce: B64, - pub base_fee: Option<u128>, + pub base_fee: Option<u64>, } impl From<Header> for PartialHeader { @@ -150,7 +150,7 @@ mod tests { difficulty: Default::default(), number: 124u64, gas_limit: Default::default(), - gas_used: 1337u128, + gas_used: 1337u64, timestamp: 0, extra_data: Default::default(), mix_hash: Default::default(), @@ -167,7 +167,7 @@ mod tests { let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); assert_eq!(header, decoded); - header.base_fee_per_gas = Some(12345u128); + header.base_fee_per_gas = Some(12345u64); let encoded = alloy_rlp::encode(&header); let decoded: Header = Header::decode(&mut encoded.as_ref()).unwrap(); @@ -190,8 +190,8 @@ mod tests { logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), difficulty: U256::from(2222), number: 0xd05u64, - gas_limit: 0x115cu128, - gas_used: 0x15b3u128, + gas_limit: 0x115cu64, + gas_used: 0x15b3u64, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), @@ -223,8 +223,8 @@ mod tests { logs_bloom: <[u8; 256]>::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap().into(), difficulty: U256::from(2222), number: 0xd05u64, - gas_limit: 0x115cu128, - gas_used: 0x15b3u128, + gas_limit: 0x115cu64, + gas_used: 0x15b3u64, timestamp: 0x1a0au64, extra_data: hex::decode("7788").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), @@ -255,8 +255,8 @@ mod tests { logs_bloom: Bloom::from_hex("00000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000").unwrap(), difficulty: U256::from(0x020000), number: 1u64, - gas_limit: U256::from(0x016345785d8a0000u128).to::<u128>(), - gas_used: U256::from(0x015534).to::<u128>(), + gas_limit: U256::from(0x016345785d8a0000u128).to::<u64>(), + gas_used: U256::from(0x015534).to::<u64>(), timestamp: 0x079e, extra_data: hex::decode("42").unwrap().into(), mix_hash: B256::from_str("0000000000000000000000000000000000000000000000000000000000000000").unwrap(), diff --git a/crates/anvil/core/src/eth/transaction/mod.rs b/crates/anvil/core/src/eth/transaction/mod.rs index e03a862b86ab3..2ed75db28f14d 100644 --- a/crates/anvil/core/src/eth/transaction/mod.rs +++ b/crates/anvil/core/src/eth/transaction/mod.rs @@ -498,7 +498,7 @@ impl PendingTransaction { value: (*value), gas_price: U256::from(*gas_price), gas_priority_fee: None, - gas_limit: *gas_limit as u64, + gas_limit: *gas_limit, access_list: vec![], ..Default::default() } @@ -524,7 +524,7 @@ impl PendingTransaction { value: *value, gas_price: U256::from(*gas_price), gas_priority_fee: None, - gas_limit: *gas_limit as u64, + gas_limit: *gas_limit, access_list: access_list.clone().into(), ..Default::default() } @@ -551,7 +551,7 @@ impl PendingTransaction { value: *value, gas_price: U256::from(*max_fee_per_gas), gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), - gas_limit: *gas_limit as u64, + gas_limit: *gas_limit, access_list: access_list.clone().into(), ..Default::default() } @@ -582,7 +582,7 @@ impl PendingTransaction { gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), max_fee_per_blob_gas: Some(U256::from(*max_fee_per_blob_gas)), blob_hashes: blob_versioned_hashes.clone(), - gas_limit: *gas_limit as u64, + gas_limit: *gas_limit, access_list: access_list.clone().into(), ..Default::default() } @@ -609,7 +609,7 @@ impl PendingTransaction { value: *value, gas_price: U256::from(*max_fee_per_gas), gas_priority_fee: Some(U256::from(*max_priority_fee_per_gas)), - gas_limit: *gas_limit as u64, + gas_limit: *gas_limit, access_list: access_list.clone().into(), authorization_list: Some(authorization_list.clone().into()), ..Default::default() @@ -637,7 +637,7 @@ impl PendingTransaction { value: *value, gas_price: U256::ZERO, gas_priority_fee: None, - gas_limit: *gas_limit as u64, + gas_limit: { *gas_limit }, access_list: vec![], optimism: OptimismFields { source_hash: Some(*source_hash), @@ -716,7 +716,7 @@ impl TypedTransaction { } } - pub fn gas_limit(&self) -> u128 { + pub fn gas_limit(&self) -> u64 { match self { Self::Legacy(tx) => tx.tx().gas_limit, Self::EIP2930(tx) => tx.tx().gas_limit, @@ -766,20 +766,23 @@ impl TypedTransaction { /// and if the transaction is EIP-4844, the result of (total blob gas cost * max fee per blob /// gas) is also added pub fn max_cost(&self) -> u128 { - let mut max_cost = self.gas_limit().saturating_mul(self.gas_price()); + let mut max_cost = (self.gas_limit() as u128).saturating_mul(self.gas_price()); if self.is_eip4844() { max_cost = max_cost.saturating_add( - self.blob_gas().unwrap_or(0).mul(self.max_fee_per_blob_gas().unwrap_or(0)), + self.blob_gas() + .map(|g| g as u128) + .unwrap_or(0) + .mul(self.max_fee_per_blob_gas().unwrap_or(0)), ) } max_cost } - pub fn blob_gas(&self) -> Option<u128> { + pub fn blob_gas(&self) -> Option<u64> { match self { - Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas() as u128), + Self::EIP4844(tx) => Some(tx.tx().tx().blob_gas()), _ => None, } } @@ -1256,7 +1259,7 @@ pub struct TransactionEssentials { pub kind: TxKind, pub input: Bytes, pub nonce: u64, - pub gas_limit: u128, + pub gas_limit: u64, pub gas_price: Option<u128>, pub max_fee_per_gas: Option<u128>, pub max_priority_fee_per_gas: Option<u128>, @@ -1279,7 +1282,7 @@ pub struct TransactionInfo { pub exit: InstructionResult, pub out: Option<Bytes>, pub nonce: u64, - pub gas_used: u128, + pub gas_used: u64, } #[derive(Debug, Serialize, Deserialize, Clone, PartialEq, Eq)] @@ -1287,9 +1290,9 @@ pub struct TransactionInfo { pub struct DepositReceipt<T = alloy_primitives::Log> { #[serde(flatten)] pub inner: ReceiptWithBloom<T>, - #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] + #[serde(default, with = "alloy_serde::quantity::opt")] pub deposit_nonce: Option<u64>, - #[serde(default, with = "alloy_serde::num::u64_opt_via_ruint")] + #[serde(default, with = "alloy_serde::quantity::opt")] pub deposit_receipt_version: Option<u64>, } @@ -1690,7 +1693,7 @@ mod tests { let tx = TxLegacy { nonce: 2u64, gas_price: 1000000000u128, - gas_limit: 100000u128, + gas_limit: 100000, to: TxKind::Call(Address::from_slice( &hex::decode("d3e8763675e4c425df46cc3b5c0f6cbdac396046").unwrap()[..], )), diff --git a/crates/anvil/core/src/eth/transaction/optimism.rs b/crates/anvil/core/src/eth/transaction/optimism.rs index 6cc7bfa5aa63c..170d67f1d9ac1 100644 --- a/crates/anvil/core/src/eth/transaction/optimism.rs +++ b/crates/anvil/core/src/eth/transaction/optimism.rs @@ -16,7 +16,7 @@ pub struct DepositTransactionRequest { pub kind: TxKind, pub mint: U256, pub value: U256, - pub gas_limit: u128, + pub gas_limit: u64, pub is_system_tx: bool, pub input: Bytes, } @@ -178,7 +178,7 @@ impl Transaction for DepositTransactionRequest { } /// Get `gas_limit`. - fn gas_limit(&self) -> u128 { + fn gas_limit(&self) -> u64 { self.gas_limit } @@ -281,7 +281,7 @@ pub struct DepositTransaction { pub kind: TxKind, pub mint: U256, pub value: U256, - pub gas_limit: u128, + pub gas_limit: u64, pub is_system_tx: bool, pub input: Bytes, } diff --git a/crates/anvil/src/cmd.rs b/crates/anvil/src/cmd.rs index 3b7778a722468..149b9c8630659 100644 --- a/crates/anvil/src/cmd.rs +++ b/crates/anvil/src/cmd.rs @@ -545,7 +545,7 @@ pub struct AnvilEvmArgs { value_name = "FEE", help_heading = "Environment config" )] - pub block_base_fee_per_gas: Option<u128>, + pub block_base_fee_per_gas: Option<u64>, /// The chain ID. #[arg(long, alias = "chain", help_heading = "Environment config")] diff --git a/crates/anvil/src/config.rs b/crates/anvil/src/config.rs index f3dbf21f6013c..189cf51752d1b 100644 --- a/crates/anvil/src/config.rs +++ b/crates/anvil/src/config.rs @@ -98,7 +98,7 @@ pub struct NodeConfig { /// Default gas price for all txs pub gas_price: Option<u128>, /// Default base fee - pub base_fee: Option<u128>, + pub base_fee: Option<u64>, /// Default blob excess gas and price pub blob_excess_gas_and_price: Option<BlobExcessGasAndPrice>, /// The hardfork to use @@ -474,9 +474,9 @@ impl NodeConfig { self } /// Returns the base fee to use - pub fn get_base_fee(&self) -> u128 { + pub fn get_base_fee(&self) -> u64 { self.base_fee - .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas)) + .or_else(|| self.genesis.as_ref().and_then(|g| g.base_fee_per_gas.map(|g| g as u64))) .unwrap_or(INITIAL_BASE_FEE) } @@ -618,7 +618,7 @@ impl NodeConfig { /// Sets the base fee #[must_use] - pub fn with_base_fee(mut self, base_fee: Option<u128>) -> Self { + pub fn with_base_fee(mut self, base_fee: Option<u64>) -> Self { self.base_fee = base_fee; self } @@ -1183,7 +1183,7 @@ latest block number: {latest_block}" // this is the base fee of the current block, but we need the base fee of // the next block let next_block_base_fee = fees.get_next_block_base_fee_per_gas( - block.header.gas_used, + block.header.gas_used as u128, gas_limit, block.header.base_fee_per_gas.unwrap_or_default(), ); @@ -1195,9 +1195,9 @@ latest block number: {latest_block}" (block.header.excess_blob_gas, block.header.blob_gas_used) { env.block.blob_excess_gas_and_price = - Some(BlobExcessGasAndPrice::new(blob_excess_gas as u64)); - let next_block_blob_excess_gas = - fees.get_next_block_blob_excess_gas(blob_excess_gas, blob_gas_used); + Some(BlobExcessGasAndPrice::new(blob_excess_gas)); + let next_block_blob_excess_gas = fees + .get_next_block_blob_excess_gas(blob_excess_gas as u128, blob_gas_used as u128); fees.set_blob_excess_gas_and_price(BlobExcessGasAndPrice::new( next_block_blob_excess_gas, )); @@ -1257,13 +1257,13 @@ latest block number: {latest_block}" chain_id, override_chain_id, timestamp: block.header.timestamp, - base_fee: block.header.base_fee_per_gas, + base_fee: block.header.base_fee_per_gas.map(|g| g as u128), timeout: self.fork_request_timeout, retries: self.fork_request_retries, backoff: self.fork_retry_backoff, compute_units_per_second: self.compute_units_per_second, total_difficulty: block.header.total_difficulty.unwrap_or_default(), - blob_gas_used: block.header.blob_gas_used, + blob_gas_used: block.header.blob_gas_used.map(|g| g as u128), blob_excess_gas_and_price: env.block.blob_excess_gas_and_price.clone(), force_transactions, }; @@ -1284,7 +1284,7 @@ latest block number: {latest_block}" if let Some(gas_limit) = self.gas_limit { return gas_limit; } else if block.header.gas_limit > 0 { - return block.header.gas_limit; + return block.header.gas_limit as u128; } } diff --git a/crates/anvil/src/eth/api.rs b/crates/anvil/src/eth/api.rs index 85d1aceaf5318..c8fd497a3e815 100644 --- a/crates/anvil/src/eth/api.rs +++ b/crates/anvil/src/eth/api.rs @@ -592,7 +592,7 @@ impl EthApi { /// Returns the current gas price pub fn gas_price(&self) -> u128 { if self.backend.is_eip1559() { - self.backend.base_fee().saturating_add(self.lowest_suggestion_tip()) + (self.backend.base_fee() as u128).saturating_add(self.lowest_suggestion_tip()) } else { self.backend.fees().raw_gas_price() } @@ -1405,7 +1405,7 @@ impl EthApi { // The spec states that `base_fee_per_gas` "[..] includes the next block after the // newest of the returned range, because this value can be derived from the // newest block" - response.base_fee_per_gas.push(self.backend.fees().base_fee()); + response.base_fee_per_gas.push(self.backend.fees().base_fee() as u128); // Same goes for the `base_fee_per_blob_gas`: // > [..] includes the next block after the newest of the returned range, because this @@ -2302,7 +2302,7 @@ impl EthApi { let to = tx.to(); let gas_price = tx.gas_price(); let value = tx.value(); - let gas = tx.gas_limit(); + let gas = tx.gas_limit() as u128; TxpoolInspectSummary { to, value, gas, gas_price } } @@ -2485,7 +2485,8 @@ impl EthApi { // get the highest possible gas limit, either the request's set value or the currently // configured gas limit - let mut highest_gas_limit = request.gas.unwrap_or(block_env.gas_limit.to()); + let mut highest_gas_limit = + request.gas.map_or(block_env.gas_limit.to::<u128>(), |g| g as u128); let gas_price = fees.gas_price.unwrap_or_default(); // If we have non-zero gas price, cap gas limit by sender balance @@ -2507,7 +2508,7 @@ impl EthApi { } let mut call_to_estimate = request.clone(); - call_to_estimate.gas = Some(highest_gas_limit); + call_to_estimate.gas = Some(highest_gas_limit as u64); // execute the call without writing to db let ethres = @@ -2541,7 +2542,7 @@ impl EthApi { // Binary search for the ideal gas limit while (highest_gas_limit - lowest_gas_limit) > 1 { - request.gas = Some(mid_gas_limit); + request.gas = Some(mid_gas_limit as u64); let ethres = self.backend.call_with_state( &state, request.clone(), @@ -2688,7 +2689,7 @@ impl EthApi { let max_fee_per_blob_gas = request.max_fee_per_blob_gas; let gas_price = request.gas_price; - let gas_limit = request.gas.unwrap_or(self.backend.gas_limit()); + let gas_limit = request.gas.unwrap_or(self.backend.gas_limit() as u64); let request = match transaction_request_to_typed(request) { Some(TypedTransactionRequest::Legacy(mut m)) => { diff --git a/crates/anvil/src/eth/backend/executor.rs b/crates/anvil/src/eth/backend/executor.rs index caa042bf48861..3221620ef0e88 100644 --- a/crates/anvil/src/eth/backend/executor.rs +++ b/crates/anvil/src/eth/backend/executor.rs @@ -28,8 +28,9 @@ use foundry_evm::{ }, }, traces::CallTraceNode, + utils::alphanet_handler_register, }; -use revm::primitives::MAX_BLOB_GAS_PER_BLOCK; +use revm::{db::WrapDatabaseRef, primitives::MAX_BLOB_GAS_PER_BLOCK}; use std::sync::Arc; /// Represents an executed transaction (transacted on the DB) @@ -38,7 +39,7 @@ pub struct ExecutedTransaction { transaction: Arc<PoolTransaction>, exit_reason: InstructionResult, out: Option<Output>, - gas_used: u128, + gas_used: u64, logs: Vec<Log>, traces: Vec<CallTraceNode>, nonce: u64, @@ -48,7 +49,7 @@ pub struct ExecutedTransaction { impl ExecutedTransaction { /// Creates the receipt for the transaction - fn create_receipt(&self, cumulative_gas_used: &mut u128) -> TypedReceipt { + fn create_receipt(&self, cumulative_gas_used: &mut u64) -> TypedReceipt { let logs = self.logs.clone(); *cumulative_gas_used = cumulative_gas_used.saturating_add(self.gas_used); @@ -56,7 +57,7 @@ impl ExecutedTransaction { let status_code = u8::from(self.exit_reason as u8 <= InstructionResult::SelfDestruct as u8); let receipt_with_bloom: ReceiptWithBloom = Receipt { status: (status_code == 1).into(), - cumulative_gas_used: *cumulative_gas_used, + cumulative_gas_used: *cumulative_gas_used as u128, logs, } .into(); @@ -101,9 +102,9 @@ pub struct TransactionExecutor<'a, Db: ?Sized, V: TransactionValidator> { pub cfg_env: CfgEnvWithHandlerCfg, pub parent_hash: B256, /// Cumulative gas used by all executed transactions - pub gas_used: u128, + pub gas_used: u64, /// Cumulative blob gas used by all executed transactions - pub blob_gas_used: u128, + pub blob_gas_used: u64, pub enable_steps_tracing: bool, pub alphanet: bool, pub print_logs: bool, @@ -118,24 +119,24 @@ impl<'a, DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'a, DB, V let mut transaction_infos = Vec::new(); let mut receipts = Vec::new(); let mut bloom = Bloom::default(); - let mut cumulative_gas_used: u128 = 0; + let mut cumulative_gas_used = 0u64; let mut invalid = Vec::new(); let mut included = Vec::new(); - let gas_limit = self.block_env.gas_limit.to::<u128>(); + let gas_limit = self.block_env.gas_limit.to::<u64>(); let parent_hash = self.parent_hash; let block_number = self.block_env.number.to::<u64>(); let difficulty = self.block_env.difficulty; let beneficiary = self.block_env.coinbase; let timestamp = self.block_env.timestamp.to::<u64>(); let base_fee = if self.cfg_env.handler_cfg.spec_id.is_enabled_in(SpecId::LONDON) { - Some(self.block_env.basefee.to::<u128>()) + Some(self.block_env.basefee.to::<u64>()) } else { None }; let is_cancun = self.cfg_env.handler_cfg.spec_id >= SpecId::CANCUN; let excess_blob_gas = if is_cancun { self.block_env.get_blob_excess_gas() } else { None }; - let mut cumulative_blob_gas_used = if is_cancun { Some(0u128) } else { None }; + let mut cumulative_blob_gas_used = if is_cancun { Some(0u64) } else { None }; for tx in self.into_iter() { let tx = match tx { @@ -172,7 +173,7 @@ impl<'a, DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'a, DB, V .blob_gas() .unwrap_or(0); cumulative_blob_gas_used = - Some(cumulative_blob_gas_used.unwrap_or(0u128).saturating_add(tx_blob_gas)); + Some(cumulative_blob_gas_used.unwrap_or(0u64).saturating_add(tx_blob_gas)); } let receipt = tx.create_receipt(&mut cumulative_gas_used); @@ -228,7 +229,7 @@ impl<'a, DB: Db + ?Sized, V: TransactionValidator> TransactionExecutor<'a, DB, V base_fee, parent_beacon_block_root: Default::default(), blob_gas_used: cumulative_blob_gas_used, - excess_blob_gas: excess_blob_gas.map(|g| g as u128), + excess_blob_gas, }; let block = Block::new(partial_header, transactions.clone(), ommers); @@ -277,16 +278,16 @@ impl<'a, 'b, DB: Db + ?Sized, V: TransactionValidator> Iterator let env = self.env_for(&transaction.pending_transaction); // check that we comply with the block's gas limit, if not disabled - let max_gas = self.gas_used.saturating_add(env.tx.gas_limit as u128); - if !env.cfg.disable_block_gas_limit && max_gas > env.block.gas_limit.to::<u128>() { + let max_gas = self.gas_used.saturating_add(env.tx.gas_limit); + if !env.cfg.disable_block_gas_limit && max_gas > env.block.gas_limit.to::<u64>() { return Some(TransactionExecutionOutcome::Exhausted(transaction)) } // check that we comply with the block's blob gas limit let max_blob_gas = self.blob_gas_used.saturating_add( - transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0u128), + transaction.pending_transaction.transaction.transaction.blob_gas().unwrap_or(0), ); - if max_blob_gas > MAX_BLOB_GAS_PER_BLOCK as u128 { + if max_blob_gas > MAX_BLOB_GAS_PER_BLOCK { return Some(TransactionExecutionOutcome::BlobGasExhausted(transaction)) } @@ -303,7 +304,7 @@ impl<'a, 'b, DB: Db + ?Sized, V: TransactionValidator> Iterator let nonce = account.nonce; // records all call and step traces - let mut inspector = Inspector::default().with_tracing().with_alphanet(self.alphanet); + let mut inspector = Inspector::default().with_tracing(); if self.enable_steps_tracing { inspector = inspector.with_steps_tracing(); } @@ -312,8 +313,7 @@ impl<'a, 'b, DB: Db + ?Sized, V: TransactionValidator> Iterator } let exec_result = { - let mut evm = - foundry_evm::utils::new_evm_with_inspector(&mut *self.db, env, &mut inspector); + let mut evm = new_evm_with_inspector(&mut *self.db, env, &mut inspector, self.alphanet); if let Some(factory) = &self.precompile_factory { inject_precompiles(&mut evm, factory.precompiles()); } @@ -364,7 +364,7 @@ impl<'a, 'b, DB: Db + ?Sized, V: TransactionValidator> Iterator trace!(target: "backend", ?exit_reason, ?gas_used, "[{:?}] executed with out={:?}", transaction.hash(), out); // Track the total gas used for total gas per block checks - self.gas_used = self.gas_used.saturating_add(gas_used as u128); + self.gas_used = self.gas_used.saturating_add(gas_used); // Track the total blob gas used for total blob gas per blob checks if let Some(blob_gas) = transaction.pending_transaction.transaction.transaction.blob_gas() { @@ -377,7 +377,7 @@ impl<'a, 'b, DB: Db + ?Sized, V: TransactionValidator> Iterator transaction, exit_reason, out, - gas_used: gas_used as u128, + gas_used, logs: logs.unwrap_or_default(), traces: inspector.tracer.map(|t| t.into_traces().into_nodes()).unwrap_or_default(), nonce, @@ -396,3 +396,37 @@ fn build_logs_bloom(logs: Vec<Log>, bloom: &mut Bloom) { } } } + +/// Creates a database with given database and inspector, optionally enabling alphanet features. +pub fn new_evm_with_inspector<DB: revm::Database>( + db: DB, + env: EnvWithHandlerCfg, + inspector: &mut dyn revm::Inspector<DB>, + alphanet: bool, +) -> revm::Evm<'_, &mut dyn revm::Inspector<DB>, DB> { + let EnvWithHandlerCfg { env, handler_cfg } = env; + + let mut handler = revm::Handler::new(handler_cfg); + + handler.append_handler_register_plain(revm::inspector_handle_register); + if alphanet { + handler.append_handler_register_plain(alphanet_handler_register); + } + + let context = revm::Context::new(revm::EvmContext::new_with_env(db, env), inspector); + + revm::Evm::new(context, handler) +} + +/// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. +pub fn new_evm_with_inspector_ref<'a, DB>( + db: DB, + env: EnvWithHandlerCfg, + inspector: &mut dyn revm::Inspector<WrapDatabaseRef<DB>>, + alphanet: bool, +) -> revm::Evm<'a, &mut dyn revm::Inspector<WrapDatabaseRef<DB>>, WrapDatabaseRef<DB>> +where + DB: revm::DatabaseRef, +{ + new_evm_with_inspector(WrapDatabaseRef(db), env, inspector, alphanet) +} diff --git a/crates/anvil/src/eth/backend/fork.rs b/crates/anvil/src/eth/backend/fork.rs index ea41b85a954c4..7d887f697572b 100644 --- a/crates/anvil/src/eth/backend/fork.rs +++ b/crates/anvil/src/eth/backend/fork.rs @@ -92,7 +92,13 @@ impl ClientFork { let total_difficulty = block.header.total_difficulty.unwrap_or_default(); let number = block.header.number; - self.config.write().update_block(number, block_hash, timestamp, base_fee, total_difficulty); + self.config.write().update_block( + number, + block_hash, + timestamp, + base_fee.map(|g| g as u128), + total_difficulty, + ); self.clear_cached_storage(); @@ -202,7 +208,7 @@ impl ClientFork { let block = block.unwrap_or_default(); let res = self.provider().estimate_gas(request).block(block.into()).await?; - Ok(res) + Ok(res as u128) } /// Sends `eth_createAccessList` diff --git a/crates/anvil/src/eth/backend/mem/inspector.rs b/crates/anvil/src/eth/backend/mem/inspector.rs index b354a9a5c1236..e590d57e35bbb 100644 --- a/crates/anvil/src/eth/backend/mem/inspector.rs +++ b/crates/anvil/src/eth/backend/mem/inspector.rs @@ -14,7 +14,6 @@ use foundry_evm::{ EvmContext, }, traces::TracingInspectorConfig, - InspectorExt, }; /// The [`revm::Inspector`] used when transacting in the evm @@ -23,8 +22,6 @@ pub struct Inspector { pub tracer: Option<TracingInspector>, /// collects all `console.sol` logs pub log_collector: Option<LogCollector>, - /// Whether to enable Alphanet support - pub alphanet: bool, } impl Inspector { @@ -59,12 +56,6 @@ impl Inspector { self.log_collector = Some(Default::default()); self } - - /// Enables Alphanet features - pub fn with_alphanet(mut self, yes: bool) -> Self { - self.alphanet = yes; - self - } } impl<DB: Database> revm::Inspector<DB> for Inspector { @@ -176,12 +167,6 @@ impl<DB: Database> revm::Inspector<DB> for Inspector { } } -impl<DB: Database> InspectorExt<DB> for Inspector { - fn is_alphanet(&self) -> bool { - self.alphanet - } -} - /// Prints all the logs pub fn print_logs(logs: &[Log]) { for log in decode_console_logs(logs) { diff --git a/crates/anvil/src/eth/backend/mem/mod.rs b/crates/anvil/src/eth/backend/mem/mod.rs index 83d8c1871fbf3..dbf22fbebe7fd 100644 --- a/crates/anvil/src/eth/backend/mem/mod.rs +++ b/crates/anvil/src/eth/backend/mem/mod.rs @@ -1,6 +1,7 @@ //! In-memory blockchain backend. use self::state::trie_storage; +use super::executor::new_evm_with_inspector_ref; use crate::{ config::PruneStateHistoryConfig, eth::{ @@ -32,6 +33,7 @@ use crate::{ revm::{db::DatabaseRef, primitives::AccountInfo}, ForkChoice, NodeConfig, PrecompileFactory, }; +use alloy_chains::NamedChain; use alloy_consensus::{Account, Header, Receipt, ReceiptWithBloom}; use alloy_eips::eip4844::MAX_BLOBS_PER_BLOCK; use alloy_primitives::{keccak256, Address, Bytes, TxHash, TxKind, B256, U256, U64}; @@ -64,8 +66,6 @@ use anvil_core::eth::{ utils::meets_eip155, }; use anvil_rpc::error::RpcError; - -use alloy_chains::NamedChain; use flate2::{read::GzDecoder, write::GzEncoder, Compression}; use foundry_evm::{ backend::{DatabaseError, DatabaseResult, RevertStateSnapshotAction}, @@ -81,8 +81,6 @@ use foundry_evm::{ }, }, traces::TracingInspectorConfig, - utils::new_evm_with_inspector_ref, - InspectorExt, }; use futures::channel::mpsc::{unbounded, UnboundedSender}; use parking_lot::{Mutex, RwLock}; @@ -460,7 +458,7 @@ impl Backend { // this is the base fee of the current block, but we need the base fee of // the next block let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - fork_block.header.gas_used, + fork_block.header.gas_used as u128, gas_limit, fork_block.header.base_fee_per_gas.unwrap_or_default(), ); @@ -665,7 +663,7 @@ impl Backend { } /// Returns the current base fee - pub fn base_fee(&self) -> u128 { + pub fn base_fee(&self) -> u64 { self.fees.base_fee() } @@ -674,7 +672,7 @@ impl Backend { } /// Sets the current basefee - pub fn set_base_fee(&self, basefee: u128) { + pub fn set_base_fee(&self, basefee: u64) { self.fees.set_base_fee(basefee) } @@ -864,15 +862,15 @@ impl Backend { &self, db: &'db dyn DatabaseRef<Error = DatabaseError>, env: EnvWithHandlerCfg, - inspector: &'i mut dyn InspectorExt< + inspector: &'i mut dyn revm::Inspector< WrapDatabaseRef<&'db dyn DatabaseRef<Error = DatabaseError>>, >, ) -> revm::Evm< '_, - &'i mut dyn InspectorExt<WrapDatabaseRef<&'db dyn DatabaseRef<Error = DatabaseError>>>, + &'i mut dyn revm::Inspector<WrapDatabaseRef<&'db dyn DatabaseRef<Error = DatabaseError>>>, WrapDatabaseRef<&'db dyn DatabaseRef<Error = DatabaseError>>, > { - let mut evm = new_evm_with_inspector_ref(db, env, inspector); + let mut evm = new_evm_with_inspector_ref(db, env, inspector, self.alphanet); if let Some(factory) = &self.precompile_factory { inject_precompiles(&mut evm, factory.precompiles()); } @@ -1121,13 +1119,13 @@ impl Backend { (outcome, header, block_hash) }; let next_block_base_fee = self.fees.get_next_block_base_fee_per_gas( - header.gas_used, - header.gas_limit, + header.gas_used as u128, + header.gas_limit as u128, header.base_fee_per_gas.unwrap_or_default(), ); let next_block_excess_blob_gas = self.fees.get_next_block_blob_excess_gas( - header.excess_blob_gas.unwrap_or_default(), - header.blob_gas_used.unwrap_or_default(), + header.excess_blob_gas.map(|g| g as u128).unwrap_or_default(), + header.blob_gas_used.map(|g| g as u128).unwrap_or_default(), ); // update next base fee @@ -1231,7 +1229,7 @@ impl Backend { env.tx = TxEnv { caller, - gas_limit: gas_limit as u64, + gas_limit, gas_price: U256::from(gas_price), gas_priority_fee: max_priority_fee_per_gas.map(U256::from), max_fee_per_blob_gas: max_fee_per_blob_gas @@ -1269,7 +1267,7 @@ impl Backend { /// Builds [`Inspector`] with the configured options fn build_inspector(&self) -> Inspector { - let mut inspector = Inspector::default().with_alphanet(self.alphanet); + let mut inspector = Inspector::default(); if self.print_logs { inspector = inspector.with_log_collector(); @@ -2230,7 +2228,7 @@ impl Backend { // Cancun specific let excess_blob_gas = block.header.excess_blob_gas; - let blob_gas_price = calc_blob_gasprice(excess_blob_gas.map_or(0, |g| g as u64)); + let blob_gas_price = calc_blob_gasprice(excess_blob_gas.unwrap_or_default()); let blob_gas_used = transaction.blob_gas(); let effective_gas_price = match transaction.transaction { @@ -2239,17 +2237,17 @@ impl Backend { TypedTransaction::EIP1559(t) => block .header .base_fee_per_gas - .unwrap_or_else(|| self.base_fee()) + .map_or(self.base_fee() as u128, |g| g as u128) .saturating_add(t.tx().max_priority_fee_per_gas), TypedTransaction::EIP4844(t) => block .header .base_fee_per_gas - .unwrap_or_else(|| self.base_fee()) + .map_or(self.base_fee() as u128, |g| g as u128) .saturating_add(t.tx().tx().max_priority_fee_per_gas), TypedTransaction::EIP7702(t) => block .header .base_fee_per_gas - .unwrap_or_else(|| self.base_fee()) + .map_or(self.base_fee() as u128, |g| g as u128) .saturating_add(t.tx().max_priority_fee_per_gas), TypedTransaction::Deposit(_) => 0_u128, }; @@ -2298,7 +2296,7 @@ impl Backend { transaction_hash: info.transaction_hash, transaction_index: Some(info.transaction_index), block_number: Some(block.header.number), - gas_used: info.gas_used, + gas_used: info.gas_used as u128, contract_address: info.contract_address, effective_gas_price, block_hash: Some(block_hash), @@ -2306,7 +2304,7 @@ impl Backend { to: info.to, state_root: Some(block.header.state_root), blob_gas_price: Some(blob_gas_price), - blob_gas_used, + blob_gas_used: blob_gas_used.map(|g| g as u128), authorization_list: None, }; @@ -2634,7 +2632,7 @@ impl TransactionValidator for Backend { } } - if tx.gas_limit() < MIN_TRANSACTION_GAS { + if tx.gas_limit() < MIN_TRANSACTION_GAS as u64 { warn!(target: "backend", "[{:?}] gas too low", tx.hash()); return Err(InvalidTransactionError::GasTooLow); } @@ -2760,7 +2758,7 @@ pub fn transaction_build( eth_transaction: MaybeImpersonatedTransaction, block: Option<&Block>, info: Option<TransactionInfo>, - base_fee: Option<u128>, + base_fee: Option<u64>, ) -> WithOtherFields<Transaction> { let mut transaction: Transaction = eth_transaction.clone().into(); if info.is_some() && transaction.transaction_type == Some(0x7E) { @@ -2774,7 +2772,7 @@ pub fn transaction_build( } else { // if transaction is already mined, gas price is considered base fee + priority fee: the // effective gas price. - let base_fee = base_fee.unwrap_or(0u128); + let base_fee = base_fee.map_or(0u128, |g| g as u128); let max_priority_fee_per_gas = transaction.max_priority_fee_per_gas.unwrap_or(0); transaction.gas_price = Some(base_fee.saturating_add(max_priority_fee_per_gas)); } diff --git a/crates/anvil/src/eth/backend/mem/storage.rs b/crates/anvil/src/eth/backend/mem/storage.rs index abc520c3b0141..29ae472071e7a 100644 --- a/crates/anvil/src/eth/backend/mem/storage.rs +++ b/crates/anvil/src/eth/backend/mem/storage.rs @@ -262,16 +262,16 @@ pub struct BlockchainStorage { impl BlockchainStorage { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option<u128>, timestamp: u64) -> Self { + pub fn new(env: &Env, base_fee: Option<u64>, timestamp: u64) -> Self { // create a dummy genesis block let partial_header = PartialHeader { timestamp, base_fee, - gas_limit: env.block.gas_limit.to::<u128>(), + gas_limit: env.block.gas_limit.to::<u64>(), beneficiary: env.block.coinbase, difficulty: env.block.difficulty, blob_gas_used: env.block.blob_excess_gas_and_price.as_ref().map(|_| 0), - excess_blob_gas: env.block.get_blob_excess_gas().map(|v| v as u128), + excess_blob_gas: env.block.get_blob_excess_gas(), ..Default::default() }; let block = Block::new::<MaybeImpersonatedTransaction>(partial_header, vec![], vec![]); @@ -423,7 +423,7 @@ pub struct Blockchain { impl Blockchain { /// Creates a new storage with a genesis block - pub fn new(env: &Env, base_fee: Option<u128>, timestamp: u64) -> Self { + pub fn new(env: &Env, base_fee: Option<u64>, timestamp: u64) -> Self { Self { storage: Arc::new(RwLock::new(BlockchainStorage::new(env, base_fee, timestamp))) } } @@ -711,7 +711,7 @@ mod tests { load_storage.load_transactions(serialized_transactions); let loaded_block = load_storage.blocks.get(&block_hash).unwrap(); - assert_eq!(loaded_block.header.gas_limit, partial_header.gas_limit); + assert_eq!(loaded_block.header.gas_limit, { partial_header.gas_limit }); let loaded_tx = loaded_block.transactions.first().unwrap(); assert_eq!(loaded_tx, &tx); } diff --git a/crates/anvil/src/eth/error.rs b/crates/anvil/src/eth/error.rs index ada9e6c532334..31d0521bb8543 100644 --- a/crates/anvil/src/eth/error.rs +++ b/crates/anvil/src/eth/error.rs @@ -388,7 +388,7 @@ impl<T: Serialize> ToRpcResponseResult for Result<T> { match err { TransportError::ErrorResp(err) => RpcError { code: ErrorCode::from(err.code), - message: err.message.into(), + message: err.message, data: err.data.and_then(|data| serde_json::to_value(data).ok()), }, err => RpcError::internal_error_with(format!("Fork Error: {err:?}")), diff --git a/crates/anvil/src/eth/fees.rs b/crates/anvil/src/eth/fees.rs index cb1b8508fd24b..72d66b1130558 100644 --- a/crates/anvil/src/eth/fees.rs +++ b/crates/anvil/src/eth/fees.rs @@ -24,7 +24,7 @@ use std::{ pub const MAX_FEE_HISTORY_CACHE_SIZE: u64 = 2048u64; /// Initial base fee for EIP-1559 blocks. -pub const INITIAL_BASE_FEE: u128 = 1_000_000_000; +pub const INITIAL_BASE_FEE: u64 = 1_000_000_000; /// Initial default gas price for the first block pub const INITIAL_GAS_PRICE: u128 = 1_875_000_000; @@ -47,7 +47,7 @@ pub struct FeeManager { /// Tracks the base fee for the next block post London /// /// This value will be updated after a new block was mined - base_fee: Arc<RwLock<u128>>, + base_fee: Arc<RwLock<u64>>, /// Tracks the excess blob gas, and the base fee, for the next block post Cancun /// /// This value will be updated after a new block was mined @@ -62,7 +62,7 @@ pub struct FeeManager { impl FeeManager { pub fn new( spec_id: SpecId, - base_fee: u128, + base_fee: u64, gas_price: u128, blob_excess_gas_and_price: BlobExcessGasAndPrice, ) -> Self { @@ -97,7 +97,7 @@ impl FeeManager { } } - pub fn base_fee(&self) -> u128 { + pub fn base_fee(&self) -> u64 { if self.is_eip1559() { *self.base_fee.read() } else { @@ -133,7 +133,7 @@ impl FeeManager { } /// Returns the current base fee - pub fn set_base_fee(&self, fee: u128) { + pub fn set_base_fee(&self, fee: u64) { trace!(target: "backend::fees", "updated base fee {:?}", fee); let mut base = self.base_fee.write(); *base = fee; @@ -151,8 +151,8 @@ impl FeeManager { &self, gas_used: u128, gas_limit: u128, - last_fee_per_gas: u128, - ) -> u128 { + last_fee_per_gas: u64, + ) -> u64 { // It's naturally impossible for base fee to be 0; // It means it was set by the user deliberately and therefore we treat it as a constant. // Therefore, we skip the base fee calculation altogether and we return 0. @@ -179,8 +179,8 @@ impl FeeManager { } /// Calculate base fee for next block. [EIP-1559](https://github.com/ethereum/EIPs/blob/master/EIPS/eip-1559.md) spec -pub fn calculate_next_block_base_fee(gas_used: u128, gas_limit: u128, base_fee: u128) -> u128 { - calc_next_block_base_fee(gas_used, gas_limit, base_fee, BaseFeeParams::ethereum()) +pub fn calculate_next_block_base_fee(gas_used: u128, gas_limit: u128, base_fee: u64) -> u64 { + calc_next_block_base_fee(gas_used as u64, gas_limit as u64, base_fee, BaseFeeParams::ethereum()) } /// An async service that takes care of the `FeeHistory` cache @@ -235,9 +235,9 @@ impl FeeHistoryService { }; let mut block_number: Option<u64> = None; - let base_fee = header.base_fee_per_gas.unwrap_or_default(); - let excess_blob_gas = header.excess_blob_gas; - let blob_gas_used = header.blob_gas_used; + let base_fee = header.base_fee_per_gas.map(|g| g as u128).unwrap_or_default(); + let excess_blob_gas = header.excess_blob_gas.map(|g| g as u128); + let blob_gas_used = header.blob_gas_used.map(|g| g as u128); let base_fee_per_blob_gas = header.blob_fee(); let mut item = FeeHistoryCacheItem { base_fee, diff --git a/crates/anvil/tests/it/anvil_api.rs b/crates/anvil/tests/it/anvil_api.rs index 31e37c98b7810..74728f94b5931 100644 --- a/crates/anvil/tests/it/anvil_api.rs +++ b/crates/anvil/tests/it/anvil_api.rs @@ -48,7 +48,7 @@ async fn can_set_block_gas_limit() { // Mine a new block, and check the new block gas limit api.mine_one().await; let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); - assert_eq!(block_gas_limit.to::<u128>(), latest_block.header.gas_limit); + assert_eq!(block_gas_limit.to::<u64>(), latest_block.header.gas_limit); } // Ref <https://github.com/foundry-rs/foundry/issues/2341> @@ -557,7 +557,7 @@ async fn test_get_transaction_receipt() { // the block should have the new base fee let block = provider.get_block(BlockId::default(), false.into()).await.unwrap().unwrap(); - assert_eq!(block.header.base_fee_per_gas.unwrap(), new_base_fee.to::<u128>()); + assert_eq!(block.header.base_fee_per_gas.unwrap(), new_base_fee.to::<u64>()); // mine blocks api.evm_mine(None).await.unwrap(); diff --git a/crates/anvil/tests/it/api.rs b/crates/anvil/tests/it/api.rs index f9aaf0dbab029..c4172b265be19 100644 --- a/crates/anvil/tests/it/api.rs +++ b/crates/anvil/tests/it/api.rs @@ -5,7 +5,10 @@ use crate::{ utils::{connect_pubsub_with_wallet, http_provider_with_signer}, }; use alloy_network::{EthereumWallet, TransactionBuilder}; -use alloy_primitives::{Address, ChainId, B256, U256}; +use alloy_primitives::{ + map::{AddressHashMap, B256HashMap, HashMap}, + Address, ChainId, B256, U256, +}; use alloy_provider::Provider; use alloy_rpc_types::{ request::TransactionRequest, state::AccountOverride, BlockId, BlockNumberOrTag, @@ -14,7 +17,7 @@ use alloy_rpc_types::{ use alloy_serde::WithOtherFields; use anvil::{eth::api::CLIENT_VERSION, spawn, NodeConfig, CHAIN_ID}; use futures::join; -use std::{collections::HashMap, time::Duration}; +use std::time::Duration; #[tokio::test(flavor = "multi_thread")] async fn can_get_block_number() { @@ -174,7 +177,7 @@ async fn can_estimate_gas_with_undersized_max_fee_per_gas() { let simple_storage_contract = SimpleStorage::deploy(&provider, init_value.clone()).await.unwrap(); - let undersized_max_fee_per_gas = 1_u128; + let undersized_max_fee_per_gas = 1; let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let latest_block_base_fee_per_gas = latest_block.header.base_fee_per_gas.unwrap(); @@ -183,7 +186,7 @@ async fn can_estimate_gas_with_undersized_max_fee_per_gas() { let estimated_gas = simple_storage_contract .setValue("new_value".to_string()) - .max_fee_per_gas(undersized_max_fee_per_gas) + .max_fee_per_gas(undersized_max_fee_per_gas.into()) .from(wallet.address()) .estimate_gas() .await @@ -255,7 +258,7 @@ async fn can_call_on_pending_block() { .call() .await .unwrap(); - assert_eq!(block.header.gas_limit, ret_gas_limit.to::<u128>()); + assert_eq!(block.header.gas_limit, ret_gas_limit.to::<u64>()); let Multicall::getCurrentBlockCoinbaseReturn { coinbase: ret_coinbase, .. } = contract .getCurrentBlockCoinbase() @@ -284,13 +287,13 @@ async fn can_call_with_undersized_max_fee_per_gas() { let latest_block = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); let latest_block_base_fee_per_gas = latest_block.header.base_fee_per_gas.unwrap(); - let undersized_max_fee_per_gas = 1_u128; + let undersized_max_fee_per_gas = 1; assert!(undersized_max_fee_per_gas < latest_block_base_fee_per_gas); let last_sender = simple_storage_contract .lastSender() - .max_fee_per_gas(undersized_max_fee_per_gas) + .max_fee_per_gas(undersized_max_fee_per_gas.into()) .from(wallet.address()) .call() .await @@ -319,23 +322,24 @@ async fn can_call_with_state_override() { // Test the `balance` account override let balance = U256::from(42u64); - let overrides = HashMap::from([( - account, - AccountOverride { balance: Some(balance), ..Default::default() }, - )]); + let mut overrides = AddressHashMap::default(); + overrides.insert(account, AccountOverride { balance: Some(balance), ..Default::default() }); let result = multicall_contract.getEthBalance(account).state(overrides).call().await.unwrap().balance; assert_eq!(result, balance); // Test the `state_diff` account override - let overrides = HashMap::from([( + let mut state_diff = B256HashMap::default(); + state_diff.insert(B256::ZERO, account.into_word()); + let mut overrides = AddressHashMap::default(); + overrides.insert( *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state_diff: Some(HashMap::from([(B256::ZERO, account.into_word())])), + state_diff: Some(state_diff), ..Default::default() }, - )]); + ); let last_sender = simple_storage_contract.lastSender().state(HashMap::default()).call().await.unwrap()._0; @@ -352,14 +356,17 @@ async fn can_call_with_state_override() { assert_eq!(value, init_value); // Test the `state` account override - let overrides = HashMap::from([( + let mut state = B256HashMap::default(); + state.insert(B256::ZERO, account.into_word()); + let mut overrides = AddressHashMap::default(); + overrides.insert( *simple_storage_contract.address(), AccountOverride { // The `lastSender` is in the first storage slot - state: Some(HashMap::from([(B256::ZERO, account.into_word())])), + state: Some(state), ..Default::default() }, - )]); + ); let last_sender = simple_storage_contract.lastSender().state(overrides.clone()).call().await.unwrap()._0; diff --git a/crates/anvil/tests/it/eip4844.rs b/crates/anvil/tests/it/eip4844.rs index 71a8bfd84c06e..a4243ce15f493 100644 --- a/crates/anvil/tests/it/eip4844.rs +++ b/crates/anvil/tests/it/eip4844.rs @@ -138,7 +138,7 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { let first_batch = vec![1u8; DATA_GAS_PER_BLOB as usize * 3]; let sidecar: SidecarBuilder<SimpleCoder> = SidecarBuilder::from_slice(&first_batch); - let num_blobs_first = sidecar.clone().take().len(); + let num_blobs_first = sidecar.clone().take().len() as u64; let sidecar = sidecar.build().unwrap(); @@ -160,7 +160,7 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { let sidecar: SidecarBuilder<SimpleCoder> = SidecarBuilder::from_slice(&second_batch); - let num_blobs_second = sidecar.clone().take().len(); + let num_blobs_second = sidecar.clone().take().len() as u64; let sidecar = sidecar.build().unwrap(); tx.set_blob_sidecar(sidecar); @@ -181,12 +181,12 @@ async fn can_mine_blobs_when_exceeds_max_blobs() { ); assert_eq!( first_block.unwrap().unwrap().header.blob_gas_used, - Some(DATA_GAS_PER_BLOB as u128 * num_blobs_first as u128) + Some(DATA_GAS_PER_BLOB * num_blobs_first) ); assert_eq!( second_block.unwrap().unwrap().header.blob_gas_used, - Some(DATA_GAS_PER_BLOB as u128 * num_blobs_second as u128) + Some(DATA_GAS_PER_BLOB * num_blobs_second) ); // Mined in two different blocks assert_eq!(first_receipt.block_number.unwrap() + 1, second_receipt.block_number.unwrap()); diff --git a/crates/anvil/tests/it/fork.rs b/crates/anvil/tests/it/fork.rs index f93fa92a19f96..e6db8a0635bbc 100644 --- a/crates/anvil/tests/it/fork.rs +++ b/crates/anvil/tests/it/fork.rs @@ -781,7 +781,7 @@ async fn test_fork_can_send_opensea_tx() { .value(U256::from(20000000000000000u64)) .with_input(input) .with_gas_price(22180711707u128) - .with_gas_limit(150_000u128); + .with_gas_limit(150_000); let tx = WithOtherFields::new(tx); let tx = provider.send_transaction(tx).await.unwrap().get_receipt().await.unwrap(); @@ -817,7 +817,7 @@ async fn test_fork_init_base_fee() { // <https://etherscan.io/block/13184859> assert_eq!(block.header.number, 13184859u64); let init_base_fee = block.header.base_fee_per_gas.unwrap(); - assert_eq!(init_base_fee, 63739886069u128); + assert_eq!(init_base_fee, 63739886069); api.mine_one().await; @@ -1185,7 +1185,7 @@ async fn test_fork_reset_basefee() { let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); // basefee of +1 block: <https://etherscan.io/block/18835001> - assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59455969592u128); + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59455969592u64); // now reset to block 18835000 -1 api.anvil_reset(Some(Forking { json_rpc_url: None, block_number: Some(18835000u64 - 1) })) @@ -1196,7 +1196,7 @@ async fn test_fork_reset_basefee() { let latest = api.block_by_number(BlockNumberOrTag::Latest).await.unwrap().unwrap(); // basefee of the forked block: <https://etherscan.io/block/18835000> - assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59017001138u128); + assert_eq!(latest.header.base_fee_per_gas.unwrap(), 59017001138); } // <https://github.com/foundry-rs/foundry/issues/6795> @@ -1288,7 +1288,7 @@ async fn test_base_fork_gas_limit() { .unwrap(); assert!(api.gas_limit() >= uint!(132_000_000_U256)); - assert!(block.header.gas_limit >= 132_000_000_u128); + assert!(block.header.gas_limit >= 132_000_000_u64); } // <https://github.com/foundry-rs/foundry/issues/7023> @@ -1443,7 +1443,7 @@ async fn test_reset_dev_account_nonce() { .from(address) .to(address) .nonce(nonce_after) - .gas_limit(21000u128), + .gas_limit(21000), )) .await .unwrap() diff --git a/crates/anvil/tests/it/gas.rs b/crates/anvil/tests/it/gas.rs index e80a52462875b..55f8321997239 100644 --- a/crates/anvil/tests/it/gas.rs +++ b/crates/anvil/tests/it/gas.rs @@ -102,7 +102,7 @@ async fn test_basefee_half_block() { .unwrap(); // unchanged, half block - assert_eq!(next_base_fee, INITIAL_BASE_FEE); + assert_eq!(next_base_fee, { INITIAL_BASE_FEE }); } #[tokio::test(flavor = "multi_thread")] @@ -147,7 +147,7 @@ async fn test_basefee_empty_block() { #[tokio::test(flavor = "multi_thread")] async fn test_respect_base_fee() { let base_fee = 50u128; - let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; let provider = handle.http_provider(); @@ -168,7 +168,7 @@ async fn test_respect_base_fee() { #[tokio::test(flavor = "multi_thread")] async fn test_tip_above_fee_cap() { let base_fee = 50u128; - let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; let provider = handle.http_provider(); @@ -190,17 +190,17 @@ async fn test_tip_above_fee_cap() { #[tokio::test(flavor = "multi_thread")] async fn test_can_use_fee_history() { let base_fee = 50u128; - let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee))).await; + let (_api, handle) = spawn(NodeConfig::test().with_base_fee(Some(base_fee as u64))).await; let provider = handle.http_provider(); for _ in 0..10 { let fee_history = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); - let next_base_fee = fee_history.base_fee_per_gas.last().unwrap(); + let next_base_fee = *fee_history.base_fee_per_gas.last().unwrap(); let tx = TransactionRequest::default() .with_to(Address::random()) .with_value(U256::from(100)) - .with_gas_price(*next_base_fee); + .with_gas_price(next_base_fee); let tx = WithOtherFields::new(tx); let receipt = @@ -208,11 +208,11 @@ async fn test_can_use_fee_history() { assert!(receipt.inner.inner.is_success()); let fee_history_after = provider.get_fee_history(1, Default::default(), &[]).await.unwrap(); - let latest_fee_history_fee = fee_history_after.base_fee_per_gas.first().unwrap(); + let latest_fee_history_fee = *fee_history_after.base_fee_per_gas.first().unwrap() as u64; let latest_block = provider.get_block(BlockId::latest(), false.into()).await.unwrap().unwrap(); - assert_eq!(latest_block.header.base_fee_per_gas.unwrap(), *latest_fee_history_fee); - assert_eq!(latest_fee_history_fee, next_base_fee); + assert_eq!(latest_block.header.base_fee_per_gas.unwrap(), latest_fee_history_fee); + assert_eq!(latest_fee_history_fee, next_base_fee as u64); } } diff --git a/crates/anvil/tests/it/optimism.rs b/crates/anvil/tests/it/optimism.rs index 6446caf9ce682..8de4eab1d1f4f 100644 --- a/crates/anvil/tests/it/optimism.rs +++ b/crates/anvil/tests/it/optimism.rs @@ -25,19 +25,21 @@ async fn test_deposits_not_supported_if_optimism_disabled() { .with_to(to) .with_value(U256::from(1234)) .with_gas_limit(21000); - let tx = WithOtherFields { - inner: tx, - other: OptimismTransactionFields { - source_hash: Some(b256!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - mint: Some(0), - is_system_tx: Some(true), - deposit_receipt_version: None, - } - .into(), + + let op_fields = OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(0), + is_system_tx: Some(true), + deposit_receipt_version: None, }; + // TODO: Test this + let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); + + let tx = WithOtherFields { inner: tx, other }; + let err = provider.send_transaction(tx).await.unwrap_err(); let s = err.to_string(); assert!(s.contains("op-stack deposit tx received but is not supported"), "{s:?}"); @@ -61,23 +63,22 @@ async fn test_send_value_deposit_transaction() { let send_value = U256::from(1234); let before_balance_to = provider.get_balance(to).await.unwrap(); + let op_fields = OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(0), + is_system_tx: Some(true), + deposit_receipt_version: None, + }; + + let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); let tx = TransactionRequest::default() .with_from(from) .with_to(to) .with_value(send_value) .with_gas_limit(21000); - let tx: WithOtherFields<TransactionRequest> = WithOtherFields { - inner: tx, - other: OptimismTransactionFields { - source_hash: Some(b256!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - mint: Some(0), - is_system_tx: Some(true), - deposit_receipt_version: None, - } - .into(), - }; + let tx: WithOtherFields<TransactionRequest> = WithOtherFields { inner: tx, other }; let pending = provider.send_transaction(tx).await.unwrap().register().await.unwrap(); @@ -121,18 +122,17 @@ async fn test_send_value_raw_deposit_transaction() { .with_gas_limit(21_000) .with_max_fee_per_gas(20_000_000_000) .with_max_priority_fee_per_gas(1_000_000_000); - let tx = WithOtherFields { - inner: tx, - other: OptimismTransactionFields { - source_hash: Some(b256!( - "0000000000000000000000000000000000000000000000000000000000000000" - )), - mint: Some(0), - is_system_tx: Some(true), - deposit_receipt_version: None, - } - .into(), + + let op_fields = OptimismTransactionFields { + source_hash: Some(b256!( + "0000000000000000000000000000000000000000000000000000000000000000" + )), + mint: Some(0), + is_system_tx: Some(true), + deposit_receipt_version: None, }; + let other = serde_json::to_value(op_fields).unwrap().try_into().unwrap(); + let tx = WithOtherFields { inner: tx, other }; let tx_envelope = tx.build(&signer).await.unwrap(); let mut tx_buffer = Vec::with_capacity(tx_envelope.encode_2718_len()); tx_envelope.encode_2718(&mut tx_buffer); diff --git a/crates/anvil/tests/it/transaction.rs b/crates/anvil/tests/it/transaction.rs index 63ebbbcf3404f..07c120d1ce525 100644 --- a/crates/anvil/tests/it/transaction.rs +++ b/crates/anvil/tests/it/transaction.rs @@ -197,7 +197,7 @@ async fn can_reject_too_high_gas_limits() { let from = accounts[0].address(); let to = accounts[1].address(); - let gas_limit = api.gas_limit().to::<u128>(); + let gas_limit = api.gas_limit().to::<u64>(); let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = @@ -237,11 +237,11 @@ async fn can_mine_large_gas_limit() { let from = accounts[0].address(); let to = accounts[1].address(); - let gas_limit = anvil::DEFAULT_GAS_LIMIT; + let gas_limit = anvil::DEFAULT_GAS_LIMIT as u64; let amount = handle.genesis_balance().checked_div(U256::from(3u64)).unwrap(); let tx = - TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit * 3); + TransactionRequest::default().to(to).value(amount).from(from).with_gas_limit(gas_limit); // send transaction with higher gas limit let pending = provider.send_transaction(WithOtherFields::new(tx)).await.unwrap(); @@ -580,7 +580,7 @@ async fn can_handle_multiple_concurrent_transfers_with_same_nonce() { .value(U256::from(100)) .from(from) .nonce(nonce) - .with_gas_limit(21000u128); + .with_gas_limit(21000); let tx = WithOtherFields::new(tx); @@ -621,7 +621,7 @@ async fn can_handle_multiple_concurrent_deploys_with_same_nonce() { .from(from) .with_input(greeter_calldata.to_owned()) .nonce(nonce) - .with_gas_limit(300_000u128); + .with_gas_limit(300_000); let tx = WithOtherFields::new(tx); @@ -662,7 +662,7 @@ async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { .from(from) .with_input(deploy_calldata.to_owned()) .nonce(nonce) - .with_gas_limit(300_000u128); + .with_gas_limit(300_000); let deploy_tx = WithOtherFields::new(deploy_tx); let set_greeting = greeter_contract.setGreeting("Hello".to_string()); @@ -672,7 +672,7 @@ async fn can_handle_multiple_concurrent_transactions_with_same_nonce() { .from(from) .with_input(set_greeting_calldata.to_owned()) .nonce(nonce) - .with_gas_limit(300_000u128); + .with_gas_limit(300_000); let set_greeting_tx = WithOtherFields::new(set_greeting_tx); for idx in 0..10 { @@ -1127,7 +1127,7 @@ async fn test_estimate_gas() { let addr = recipient; let account_override = AccountOverride { balance: Some(alloy_primitives::U256::from(1e18)), ..Default::default() }; - let mut state_override = StateOverride::new(); + let mut state_override = StateOverride::default(); state_override.insert(addr, account_override); // Estimate gas with state override implying sufficient funds. @@ -1152,7 +1152,7 @@ async fn test_reject_gas_too_low() { .to(Address::random()) .value(U256::from(1337u64)) .from(account) - .with_gas_limit(gas as u128); + .with_gas_limit(gas); let tx = WithOtherFields::new(tx); let resp = provider.send_transaction(tx).await; @@ -1169,7 +1169,7 @@ async fn can_call_with_high_gas_limit() { let greeter_contract = Greeter::deploy(provider, "Hello World!".to_string()).await.unwrap(); - let greeting = greeter_contract.greet().gas(60_000_000u128).call().await.unwrap(); + let greeting = greeter_contract.greet().gas(60_000_000).call().await.unwrap(); assert_eq!("Hello World!", greeting._0); } @@ -1179,7 +1179,7 @@ async fn test_reject_eip1559_pre_london() { spawn(NodeConfig::test().with_hardfork(Some(EthereumHardfork::Berlin.into()))).await; let provider = handle.http_provider(); - let gas_limit = api.gas_limit().to::<u128>(); + let gas_limit = api.gas_limit().to::<u64>(); let gas_price = api.gas_price(); let unsupported_call_builder = diff --git a/crates/cast/tests/cli/main.rs b/crates/cast/tests/cli/main.rs index 8c928b3a8a535..df566d1144c0b 100644 --- a/crates/cast/tests/cli/main.rs +++ b/crates/cast/tests/cli/main.rs @@ -54,6 +54,7 @@ mixHash [..] nonce [..] number [..] parentHash [..] +parentBeaconRoot [..] transactionsRoot [..] receiptsRoot [..] sha3Uncles [..] diff --git a/crates/cheatcodes/assets/cheatcodes.json b/crates/cheatcodes/assets/cheatcodes.json index 904627b915453..e6e573b83e8ef 100644 --- a/crates/cheatcodes/assets/cheatcodes.json +++ b/crates/cheatcodes/assets/cheatcodes.json @@ -5596,7 +5596,7 @@ { "func": { "id": "lastCallGas", - "description": "Gets the gas used in the last call.", + "description": "Gets the gas used in the last call from the callee perspective.", "declaration": "function lastCallGas() external view returns (Gas memory gas);", "visibility": "external", "mutability": "view", @@ -8599,6 +8599,46 @@ }, "safety": "unsafe" }, + { + "func": { + "id": "snapshotGasLastCall_0", + "description": "Snapshot capture the gas usage of the last call by name from the callee perspective.", + "declaration": "function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "snapshotGasLastCall(string)", + "selector": "0xdd9fca12", + "selectorBytes": [ + 221, + 159, + 202, + 18 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotGasLastCall_1", + "description": "Snapshot capture the gas usage of the last call by name in a group from the callee perspective.", + "declaration": "function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "snapshotGasLastCall(string,string)", + "selector": "0x200c6772", + "selectorBytes": [ + 32, + 12, + 103, + 114 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "snapshotState", @@ -8619,6 +8659,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "snapshotValue_0", + "description": "Snapshot capture an arbitrary numerical value by name.\nThe group name is derived from the contract name.", + "declaration": "function snapshotValue(string calldata name, uint256 value) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,uint256)", + "selector": "0x51db805a", + "selectorBytes": [ + 81, + 219, + 128, + 90 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "snapshotValue_1", + "description": "Snapshot capture an arbitrary numerical value by name in a group.", + "declaration": "function snapshotValue(string calldata group, string calldata name, uint256 value) external;", + "visibility": "external", + "mutability": "", + "signature": "snapshotValue(string,string,uint256)", + "selector": "0x6d2b27d8", + "selectorBytes": [ + 109, + 43, + 39, + 216 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "split", @@ -8779,6 +8859,46 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "startSnapshotGas_0", + "description": "Start a snapshot capture of the current gas usage by name.\nThe group name is derived from the contract name.", + "declaration": "function startSnapshotGas(string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "startSnapshotGas(string)", + "selector": "0x3cad9d7b", + "selectorBytes": [ + 60, + 173, + 157, + 123 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "startSnapshotGas_1", + "description": "Start a snapshot capture of the current gas usage by name in a group.", + "declaration": "function startSnapshotGas(string calldata group, string calldata name) external;", + "visibility": "external", + "mutability": "", + "signature": "startSnapshotGas(string,string)", + "selector": "0x6cd0cc53", + "selectorBytes": [ + 108, + 208, + 204, + 83 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "startStateDiffRecording", @@ -8919,6 +9039,66 @@ "status": "stable", "safety": "unsafe" }, + { + "func": { + "id": "stopSnapshotGas_0", + "description": "Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start.", + "declaration": "function stopSnapshotGas() external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas()", + "selector": "0xf6402eda", + "selectorBytes": [ + 246, + 64, + 46, + 218 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopSnapshotGas_1", + "description": "Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start.\nThe group name is derived from the contract name.", + "declaration": "function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas(string)", + "selector": "0x773b2805", + "selectorBytes": [ + 119, + 59, + 40, + 5 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, + { + "func": { + "id": "stopSnapshotGas_2", + "description": "Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start.", + "declaration": "function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed);", + "visibility": "external", + "mutability": "", + "signature": "stopSnapshotGas(string,string)", + "selector": "0x0c9db707", + "selectorBytes": [ + 12, + 157, + 183, + 7 + ] + }, + "group": "evm", + "status": "stable", + "safety": "unsafe" + }, { "func": { "id": "store", diff --git a/crates/cheatcodes/spec/src/vm.rs b/crates/cheatcodes/spec/src/vm.rs index bff0d1c1e109d..656087d91a38d 100644 --- a/crates/cheatcodes/spec/src/vm.rs +++ b/crates/cheatcodes/spec/src/vm.rs @@ -542,6 +542,49 @@ interface Vm { #[cheatcode(group = Evm, safety = Unsafe)] function readCallers() external returns (CallerMode callerMode, address msgSender, address txOrigin); + // ----- Arbitrary Snapshots ----- + + /// Snapshot capture an arbitrary numerical value by name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(string calldata name, uint256 value) external; + + /// Snapshot capture an arbitrary numerical value by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotValue(string calldata group, string calldata name, uint256 value) external; + + // -------- Gas Snapshots -------- + + /// Snapshot capture the gas usage of the last call by name from the callee perspective. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed); + + /// Snapshot capture the gas usage of the last call by name in a group from the callee perspective. + #[cheatcode(group = Evm, safety = Unsafe)] + function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed); + + /// Start a snapshot capture of the current gas usage by name. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas(string calldata name) external; + + /// Start a snapshot capture of the current gas usage by name in a group. + #[cheatcode(group = Evm, safety = Unsafe)] + function startSnapshotGas(string calldata group, string calldata name) external; + + /// Stop the snapshot capture of the current gas by latest snapshot name, capturing the gas used since the start. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas() external returns (uint256 gasUsed); + + /// Stop the snapshot capture of the current gas usage by name, capturing the gas used since the start. + /// The group name is derived from the contract name. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed); + + /// Stop the snapshot capture of the current gas usage by name in a group, capturing the gas used since the start. + #[cheatcode(group = Evm, safety = Unsafe)] + function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); + // -------- State Snapshots -------- /// `snapshot` is being deprecated in favor of `snapshotState`. It will be removed in future versions. @@ -731,7 +774,7 @@ interface Vm { // -------- Gas Measurement -------- - /// Gets the gas used in the last call. + /// Gets the gas used in the last call from the callee perspective. #[cheatcode(group = Evm, safety = Safe)] function lastCallGas() external view returns (Gas memory gas); diff --git a/crates/cheatcodes/src/config.rs b/crates/cheatcodes/src/config.rs index 531784e16339d..c6a15f45dfd31 100644 --- a/crates/cheatcodes/src/config.rs +++ b/crates/cheatcodes/src/config.rs @@ -49,6 +49,8 @@ pub struct CheatsConfig { /// If Some, `vm.getDeployedCode` invocations are validated to be in scope of this list. /// If None, no validation is performed. pub available_artifacts: Option<ContractsByArtifact>, + /// Name of the script/test contract which is currently running. + pub running_contract: Option<String>, /// Version of the script/test contract which is currently running. pub running_version: Option<Version>, /// Whether to enable legacy (non-reverting) assertions. @@ -64,6 +66,7 @@ impl CheatsConfig { evm_opts: EvmOpts, available_artifacts: Option<ContractsByArtifact>, script_wallets: Option<ScriptWallets>, + running_contract: Option<String>, running_version: Option<Version>, ) -> Self { let mut allowed_paths = vec![config.root.0.clone()]; @@ -92,6 +95,7 @@ impl CheatsConfig { labels: config.labels.clone(), script_wallets, available_artifacts, + running_contract, running_version, assertions_revert: config.assertions_revert, seed: config.fuzz.seed, @@ -221,6 +225,7 @@ impl Default for CheatsConfig { labels: Default::default(), script_wallets: None, available_artifacts: Default::default(), + running_contract: Default::default(), running_version: Default::default(), assertions_revert: true, seed: None, @@ -240,6 +245,7 @@ mod tests { None, None, None, + None, ) } diff --git a/crates/cheatcodes/src/evm.rs b/crates/cheatcodes/src/evm.rs index a201ec577e076..d4ac7172221f4 100644 --- a/crates/cheatcodes/src/evm.rs +++ b/crates/cheatcodes/src/evm.rs @@ -1,8 +1,9 @@ //! Implementations of [`Evm`](spec::Group::Evm) cheatcodes. use crate::{ - inspector::RecordDebugStepInfo, BroadcastableTransaction, Cheatcode, Cheatcodes, - CheatcodesExecutor, CheatsCtxt, Error, Result, Vm::*, + inspector::InnerEcx, inspector::RecordDebugStepInfo, BroadcastableTransaction, Cheatcode, Cheatcodes, + CheatcodesExecutor, + CheatsCtxt, Error, Result, Vm::*, }; use alloy_consensus::TxEnvelope; use alloy_genesis::{Genesis, GenesisAccount}; @@ -16,10 +17,7 @@ use foundry_evm_core::{ }; use foundry_evm_traces::StackSnapshotType; use rand::Rng; -use revm::{ - primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}, - InnerEvmContext, -}; +use revm::primitives::{Account, Bytecode, SpecId, KECCAK_EMPTY}; use std::{ collections::{BTreeMap, HashMap}, path::Path, @@ -57,6 +55,19 @@ impl RecordAccess { } } +/// Records the `snapshotGas*` cheatcodes. +#[derive(Clone, Debug)] +pub struct GasRecord { + /// The group name of the gas snapshot. + pub group: String, + /// The name of the gas snapshot. + pub name: String, + /// The total gas used in the gas snapshot. + pub gas_used: u64, + /// Depth at which the gas snapshot was taken. + pub depth: u64, +} + /// Records `deal` cheatcodes #[derive(Clone, Debug)] pub struct DealRecord { @@ -77,21 +88,21 @@ impl Cheatcode for addrCall { } impl Cheatcode for getNonce_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; get_nonce(ccx, account) } } impl Cheatcode for getNonce_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { wallet } = self; get_nonce(ccx, &wallet.addr) } } impl Cheatcode for loadCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot } = *self; ensure_not_precompile!(&target, ccx); ccx.ecx.load_account(target)?; @@ -128,7 +139,7 @@ impl Cheatcode for loadCall { } impl Cheatcode for loadAllocsCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { pathToAllocsJson } = self; let path = Path::new(pathToAllocsJson); @@ -154,7 +165,7 @@ impl Cheatcode for loadAllocsCall { } impl Cheatcode for dumpStateCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { pathToStateJson } = self; let path = Path::new(pathToStateJson); @@ -274,7 +285,7 @@ impl Cheatcode for lastCallGasCall { } impl Cheatcode for chainIdCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newChainId } = self; ensure!(*newChainId <= U256::from(u64::MAX), "chain ID must be less than 2^64 - 1"); ccx.ecx.env.cfg.chain_id = newChainId.to(); @@ -283,7 +294,7 @@ impl Cheatcode for chainIdCall { } impl Cheatcode for coinbaseCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newCoinbase } = self; ccx.ecx.env.block.coinbase = *newCoinbase; Ok(Default::default()) @@ -291,7 +302,7 @@ impl Cheatcode for coinbaseCall { } impl Cheatcode for difficultyCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newDifficulty } = self; ensure!( ccx.ecx.spec_id() < SpecId::MERGE, @@ -304,7 +315,7 @@ impl Cheatcode for difficultyCall { } impl Cheatcode for feeCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBasefee } = self; ccx.ecx.env.block.basefee = *newBasefee; Ok(Default::default()) @@ -312,7 +323,7 @@ impl Cheatcode for feeCall { } impl Cheatcode for prevrandao_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.spec_id() >= SpecId::MERGE, @@ -325,7 +336,7 @@ impl Cheatcode for prevrandao_0Call { } impl Cheatcode for prevrandao_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newPrevrandao } = self; ensure!( ccx.ecx.spec_id() >= SpecId::MERGE, @@ -338,7 +349,7 @@ impl Cheatcode for prevrandao_1Call { } impl Cheatcode for blobhashesCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { hashes } = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -351,7 +362,7 @@ impl Cheatcode for blobhashesCall { } impl Cheatcode for getBlobhashesCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -363,7 +374,7 @@ impl Cheatcode for getBlobhashesCall { } impl Cheatcode for rollCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newHeight } = self; ccx.ecx.env.block.number = *newHeight; Ok(Default::default()) @@ -371,14 +382,14 @@ impl Cheatcode for rollCall { } impl Cheatcode for getBlockNumberCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.number.abi_encode()) } } impl Cheatcode for txGasPriceCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newGasPrice } = self; ccx.ecx.env.tx.gas_price = *newGasPrice; Ok(Default::default()) @@ -386,7 +397,7 @@ impl Cheatcode for txGasPriceCall { } impl Cheatcode for warpCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newTimestamp } = self; ccx.ecx.env.block.timestamp = *newTimestamp; Ok(Default::default()) @@ -394,14 +405,14 @@ impl Cheatcode for warpCall { } impl Cheatcode for getBlockTimestampCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.timestamp.abi_encode()) } } impl Cheatcode for blobBaseFeeCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { newBlobBaseFee } = self; ensure!( ccx.ecx.spec_id() >= SpecId::CANCUN, @@ -414,14 +425,14 @@ impl Cheatcode for blobBaseFeeCall { } impl Cheatcode for getBlobBaseFeeCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; Ok(ccx.ecx.env.block.get_blob_excess_gas().unwrap_or(0).abi_encode()) } } impl Cheatcode for dealCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account: address, newBalance: new_balance } = *self; let account = journaled_account(ccx.ecx, address)?; let old_balance = std::mem::replace(&mut account.info.balance, new_balance); @@ -432,7 +443,7 @@ impl Cheatcode for dealCall { } impl Cheatcode for etchCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, newRuntimeBytecode } = self; ensure_not_precompile!(target, ccx); ccx.ecx.load_account(*target)?; @@ -443,7 +454,7 @@ impl Cheatcode for etchCall { } impl Cheatcode for resetNonceCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; let account = journaled_account(ccx.ecx, *account)?; // Per EIP-161, EOA nonces start at 0, but contract nonces @@ -458,7 +469,7 @@ impl Cheatcode for resetNonceCall { } impl Cheatcode for setNonceCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; // nonce must increment only @@ -474,7 +485,7 @@ impl Cheatcode for setNonceCall { } impl Cheatcode for setNonceUnsafeCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account, newNonce } = *self; let account = journaled_account(ccx.ecx, account)?; account.info.nonce = newNonce; @@ -483,7 +494,7 @@ impl Cheatcode for setNonceUnsafeCall { } impl Cheatcode for storeCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target, slot, value } = *self; ensure_not_precompile!(&target, ccx); // ensure the account is touched @@ -494,7 +505,7 @@ impl Cheatcode for storeCall { } impl Cheatcode for coolCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target } = self; if let Some(account) = ccx.ecx.journaled_state.state.get_mut(target) { account.unmark_touch(); @@ -505,22 +516,96 @@ impl Cheatcode for coolCall { } impl Cheatcode for readCallersCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; read_callers(ccx.state, &ccx.ecx.env.tx.caller) } } +impl Cheatcode for snapshotValue_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, value } = self; + inner_value_snapshot(ccx, None, Some(name.clone()), value.to_string()) + } +} + +impl Cheatcode for snapshotValue_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name, value } = self; + inner_value_snapshot(ccx, Some(group.clone()), Some(name.clone()), value.to_string()) + } +} + +impl Cheatcode for snapshotGasLastCall_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { + bail!("no external call was made yet"); + }; + inner_last_gas_snapshot(ccx, None, Some(name.clone()), last_call_gas.gasTotalUsed) + } +} + +impl Cheatcode for snapshotGasLastCall_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name, group } = self; + let Some(last_call_gas) = &ccx.state.gas_metering.last_call_gas else { + bail!("no external call was made yet"); + }; + inner_last_gas_snapshot( + ccx, + Some(group.clone()), + Some(name.clone()), + last_call_gas.gasTotalUsed, + ) + } +} + +impl Cheatcode for startSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + inner_start_gas_snapshot(ccx, None, Some(name.clone())) + } +} + +impl Cheatcode for startSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + inner_start_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + } +} + +impl Cheatcode for stopSnapshotGas_0Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self {} = self; + inner_stop_gas_snapshot(ccx, None, None) + } +} + +impl Cheatcode for stopSnapshotGas_1Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { name } = self; + inner_stop_gas_snapshot(ccx, None, Some(name.clone())) + } +} + +impl Cheatcode for stopSnapshotGas_2Call { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { + let Self { group, name } = self; + inner_stop_gas_snapshot(ccx, Some(group.clone()), Some(name.clone())) + } +} + // Deprecated in favor of `snapshotStateCall` impl Cheatcode for snapshotCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; inner_snapshot_state(ccx) } } impl Cheatcode for snapshotStateCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; inner_snapshot_state(ccx) } @@ -528,14 +613,14 @@ impl Cheatcode for snapshotStateCall { // Deprecated in favor of `revertToStateCall` impl Cheatcode for revertToCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } } impl Cheatcode for revertToStateCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; inner_revert_to_state(ccx, *snapshotId) } @@ -543,14 +628,14 @@ impl Cheatcode for revertToStateCall { // Deprecated in favor of `revertToStateAndDeleteCall` impl Cheatcode for revertToAndDeleteCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } } impl Cheatcode for revertToStateAndDeleteCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; inner_revert_to_state_and_delete(ccx, *snapshotId) } @@ -558,14 +643,14 @@ impl Cheatcode for revertToStateAndDeleteCall { // Deprecated in favor of `deleteStateSnapshotCall` impl Cheatcode for deleteSnapshotCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; inner_delete_state_snapshot(ccx, *snapshotId) } } impl Cheatcode for deleteStateSnapshotCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { snapshotId } = self; inner_delete_state_snapshot(ccx, *snapshotId) } @@ -573,14 +658,14 @@ impl Cheatcode for deleteStateSnapshotCall { // Deprecated in favor of `deleteStateSnapshotsCall` impl Cheatcode for deleteSnapshotsCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; inner_delete_state_snapshots(ccx) } } impl Cheatcode for deleteStateSnapshotsCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; inner_delete_state_snapshots(ccx) } @@ -602,11 +687,7 @@ impl Cheatcode for stopAndReturnStateDiffCall { } impl Cheatcode for broadcastRawTransactionCall { - fn apply_full<DB: DatabaseExt, E: CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let mut data = self.data.as_ref(); let tx = TxEnvelope::decode(&mut data) .map_err(|err| fmt_err!("failed to decode RLP-encoded transaction: {err}"))?; @@ -615,7 +696,7 @@ impl Cheatcode for broadcastRawTransactionCall { tx.clone().into(), &ccx.ecx.env, &mut ccx.ecx.journaled_state, - &mut executor.get_inspector(ccx.state), + &mut *executor.get_inspector(ccx.state), )?; if ccx.state.broadcast.is_some() { @@ -630,7 +711,7 @@ impl Cheatcode for broadcastRawTransactionCall { } impl Cheatcode for setBlockhashCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber, blockHash } = *self; ensure!( blockNumber <= ccx.ecx.env.block.number, @@ -644,11 +725,7 @@ impl Cheatcode for setBlockhashCall { } impl Cheatcode for startDebugTraceRecordingCall { - fn apply_full<DB: DatabaseExt, E: CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else { return Err(Error::from("no tracer initiated, consider adding -vvv flag")) }; @@ -679,11 +756,7 @@ impl Cheatcode for startDebugTraceRecordingCall { } impl Cheatcode for stopAndReturnDebugTraceRecordingCall { - fn apply_full<DB: DatabaseExt, E: CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_mut()) else { return Err(Error::from("no tracer initiated, consider adding -vvv flag")) }; @@ -709,16 +782,16 @@ impl Cheatcode for stopAndReturnDebugTraceRecordingCall { } } -pub(super) fn get_nonce<DB: DatabaseExt>(ccx: &mut CheatsCtxt<DB>, address: &Address) -> Result { +pub(super) fn get_nonce(ccx: &mut CheatsCtxt, address: &Address) -> Result { let account = ccx.ecx.journaled_state.load_account(*address, &mut ccx.ecx.db)?; Ok(account.info.nonce.abi_encode()) } -fn inner_snapshot_state<DB: DatabaseExt>(ccx: &mut CheatsCtxt<DB>) -> Result { +fn inner_snapshot_state(ccx: &mut CheatsCtxt) -> Result { Ok(ccx.ecx.db.snapshot_state(&ccx.ecx.journaled_state, &ccx.ecx.env).abi_encode()) } -fn inner_revert_to_state<DB: DatabaseExt>(ccx: &mut CheatsCtxt<DB>, snapshot_id: U256) -> Result { +fn inner_revert_to_state(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { let result = if let Some(journaled_state) = ccx.ecx.db.revert_state( snapshot_id, &ccx.ecx.journaled_state, @@ -734,10 +807,7 @@ fn inner_revert_to_state<DB: DatabaseExt>(ccx: &mut CheatsCtxt<DB>, snapshot_id: Ok(result.abi_encode()) } -fn inner_revert_to_state_and_delete<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, - snapshot_id: U256, -) -> Result { +fn inner_revert_to_state_and_delete(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { let result = if let Some(journaled_state) = ccx.ecx.db.revert_state( snapshot_id, &ccx.ecx.journaled_state, @@ -753,19 +823,132 @@ fn inner_revert_to_state_and_delete<DB: DatabaseExt>( Ok(result.abi_encode()) } -fn inner_delete_state_snapshot<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, - snapshot_id: U256, -) -> Result { +fn inner_delete_state_snapshot(ccx: &mut CheatsCtxt, snapshot_id: U256) -> Result { let result = ccx.ecx.db.delete_state_snapshot(snapshot_id); Ok(result.abi_encode()) } -fn inner_delete_state_snapshots<DB: DatabaseExt>(ccx: &mut CheatsCtxt<DB>) -> Result { +fn inner_delete_state_snapshots(ccx: &mut CheatsCtxt) -> Result { ccx.ecx.db.delete_state_snapshots(); Ok(Default::default()) } +fn inner_value_snapshot( + ccx: &mut CheatsCtxt, + group: Option<String>, + name: Option<String>, + value: String, +) -> Result { + let (group, name) = derive_snapshot_name(ccx, group, name); + + ccx.state.gas_snapshots.entry(group).or_default().insert(name, value); + + Ok(Default::default()) +} + +fn inner_last_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option<String>, + name: Option<String>, + value: u64, +) -> Result { + let (group, name) = derive_snapshot_name(ccx, group, name); + + ccx.state.gas_snapshots.entry(group).or_default().insert(name, value.to_string()); + + Ok(value.abi_encode()) +} + +fn inner_start_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option<String>, + name: Option<String>, +) -> Result { + // Revert if there is an active gas snapshot as we can only have one active snapshot at a time. + if ccx.state.gas_metering.active_gas_snapshot.is_some() { + let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone(); + bail!("gas snapshot was already started with group: {group} and name: {name}"); + } + + let (group, name) = derive_snapshot_name(ccx, group, name); + + ccx.state.gas_metering.gas_records.push(GasRecord { + group: group.clone(), + name: name.clone(), + gas_used: 0, + depth: ccx.ecx.journaled_state.depth(), + }); + + ccx.state.gas_metering.active_gas_snapshot = Some((group, name)); + + ccx.state.gas_metering.start(); + + Ok(Default::default()) +} + +fn inner_stop_gas_snapshot( + ccx: &mut CheatsCtxt, + group: Option<String>, + name: Option<String>, +) -> Result { + // If group and name are not provided, use the last snapshot group and name. + let (group, name) = group.zip(name).unwrap_or_else(|| { + let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone(); + (group, name) + }); + + if let Some(record) = ccx + .state + .gas_metering + .gas_records + .iter_mut() + .find(|record| record.group == group && record.name == name) + { + // Calculate the gas used since the snapshot was started. + // We subtract 171 from the gas used to account for gas used by the snapshot itself. + let value = record.gas_used.saturating_sub(171); + + ccx.state + .gas_snapshots + .entry(group.clone()) + .or_default() + .insert(name.clone(), value.to_string()); + + // Stop the gas metering. + ccx.state.gas_metering.stop(); + + // Remove the gas record. + ccx.state + .gas_metering + .gas_records + .retain(|record| record.group != group && record.name != name); + + // Clear last snapshot cache if we have an exact match. + if let Some((snapshot_group, snapshot_name)) = &ccx.state.gas_metering.active_gas_snapshot { + if snapshot_group == &group && snapshot_name == &name { + ccx.state.gas_metering.active_gas_snapshot = None; + } + } + + Ok(value.abi_encode()) + } else { + bail!("no gas snapshot was started with the name: {name} in group: {group}"); + } +} + +// Derives the snapshot group and name from the provided group and name or the running contract. +fn derive_snapshot_name( + ccx: &CheatsCtxt, + group: Option<String>, + name: Option<String>, +) -> (String, String) { + let group = group.unwrap_or_else(|| { + ccx.state.config.running_contract.clone().expect("expected running contract") + }); + let name = name.unwrap_or_else(|| "default".to_string()); + (group, name) +} + /// Reads the current caller information and returns the current [CallerMode], `msg.sender` and /// `tx.origin`. /// @@ -815,10 +998,10 @@ fn read_callers(state: &Cheatcodes, default_sender: &Address) -> Result { } /// Ensures the `Account` is loaded and touched. -pub(super) fn journaled_account<DB: DatabaseExt>( - ecx: &mut InnerEvmContext<DB>, +pub(super) fn journaled_account<'a>( + ecx: InnerEcx<'a, '_, '_>, addr: Address, -) -> Result<&mut Account> { +) -> Result<&'a mut Account> { ecx.load_account(addr)?; ecx.journaled_state.touch(&addr); Ok(ecx.journaled_state.state.get_mut(&addr).expect("account is loaded")) diff --git a/crates/cheatcodes/src/evm/fork.rs b/crates/cheatcodes/src/evm/fork.rs index a8cc830009fec..84fda7883c7d5 100644 --- a/crates/cheatcodes/src/evm/fork.rs +++ b/crates/cheatcodes/src/evm/fork.rs @@ -1,4 +1,4 @@ -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, DatabaseExt, Result, Vm::*}; use alloy_dyn_abi::DynSolValue; use alloy_primitives::{B256, U256}; use alloy_provider::Provider; @@ -8,7 +8,7 @@ use foundry_common::provider::ProviderBuilder; use foundry_evm_core::fork::CreateFork; impl Cheatcode for activeForkCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.ecx .db @@ -19,49 +19,49 @@ impl Cheatcode for activeForkCall { } impl Cheatcode for createFork_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias } = self; create_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createFork_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, blockNumber } = self; create_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createFork_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, txHash } = self; create_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for createSelectFork_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias } = self; create_select_fork(ccx, urlOrAlias, None) } } impl Cheatcode for createSelectFork_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, blockNumber } = self; create_select_fork(ccx, urlOrAlias, Some(blockNumber.saturating_to())) } } impl Cheatcode for createSelectFork_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { urlOrAlias, txHash } = self; create_select_fork_at_transaction(ccx, urlOrAlias, txHash) } } impl Cheatcode for rollFork_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { blockNumber } = self; persist_caller(ccx); ccx.ecx.db.roll_fork( @@ -75,7 +75,7 @@ impl Cheatcode for rollFork_0Call { } impl Cheatcode for rollFork_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { txHash } = self; persist_caller(ccx); ccx.ecx.db.roll_fork_to_transaction( @@ -89,7 +89,7 @@ impl Cheatcode for rollFork_1Call { } impl Cheatcode for rollFork_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, blockNumber } = self; persist_caller(ccx); ccx.ecx.db.roll_fork( @@ -103,7 +103,7 @@ impl Cheatcode for rollFork_2Call { } impl Cheatcode for rollFork_3Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId, txHash } = self; persist_caller(ccx); ccx.ecx.db.roll_fork_to_transaction( @@ -117,7 +117,7 @@ impl Cheatcode for rollFork_3Call { } impl Cheatcode for selectForkCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { forkId } = self; persist_caller(ccx); check_broadcast(ccx.state)?; @@ -128,11 +128,7 @@ impl Cheatcode for selectForkCall { } impl Cheatcode for transact_0Call { - fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let Self { txHash } = *self; ccx.ecx.db.transact( None, @@ -146,25 +142,21 @@ impl Cheatcode for transact_0Call { } impl Cheatcode for transact_1Call { - fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let Self { forkId, txHash } = *self; ccx.ecx.db.transact( Some(forkId), txHash, &mut ccx.ecx.env, &mut ccx.ecx.journaled_state, - &mut executor.get_inspector(ccx.state), + &mut *executor.get_inspector(ccx.state), )?; Ok(Default::default()) } } impl Cheatcode for allowCheatcodesCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.allow_cheatcode_access(*account); Ok(Default::default()) @@ -172,7 +164,7 @@ impl Cheatcode for allowCheatcodesCall { } impl Cheatcode for makePersistent_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.add_persistent_account(*account); Ok(Default::default()) @@ -180,7 +172,7 @@ impl Cheatcode for makePersistent_0Call { } impl Cheatcode for makePersistent_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1 } = self; ccx.ecx.db.add_persistent_account(*account0); ccx.ecx.db.add_persistent_account(*account1); @@ -189,7 +181,7 @@ impl Cheatcode for makePersistent_1Call { } impl Cheatcode for makePersistent_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account0, account1, account2 } = self; ccx.ecx.db.add_persistent_account(*account0); ccx.ecx.db.add_persistent_account(*account1); @@ -199,7 +191,7 @@ impl Cheatcode for makePersistent_2Call { } impl Cheatcode for makePersistent_3Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; for account in accounts { ccx.ecx.db.add_persistent_account(*account); @@ -209,7 +201,7 @@ impl Cheatcode for makePersistent_3Call { } impl Cheatcode for revokePersistent_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; ccx.ecx.db.remove_persistent_account(account); Ok(Default::default()) @@ -217,7 +209,7 @@ impl Cheatcode for revokePersistent_0Call { } impl Cheatcode for revokePersistent_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { accounts } = self; for account in accounts { ccx.ecx.db.remove_persistent_account(account); @@ -227,14 +219,14 @@ impl Cheatcode for revokePersistent_1Call { } impl Cheatcode for isPersistentCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { account } = self; Ok(ccx.ecx.db.is_persistent(account).abi_encode()) } } impl Cheatcode for rpc_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { method, params } = self; let url = ccx.ecx.db.active_fork_url().ok_or_else(|| fmt_err!("no active fork URL found"))?; @@ -251,7 +243,7 @@ impl Cheatcode for rpc_1Call { } impl Cheatcode for eth_getLogsCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { fromBlock, toBlock, target, topics } = self; let (Ok(from_block), Ok(to_block)) = (u64::try_from(fromBlock), u64::try_from(toBlock)) else { @@ -293,11 +285,7 @@ impl Cheatcode for eth_getLogsCall { } /// Creates and then also selects the new fork -fn create_select_fork<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, - url_or_alias: &str, - block: Option<u64>, -) -> Result { +fn create_select_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option<u64>) -> Result { check_broadcast(ccx.state)?; let fork = create_fork_request(ccx, url_or_alias, block)?; @@ -306,19 +294,15 @@ fn create_select_fork<DB: DatabaseExt>( } /// Creates a new fork -fn create_fork<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, - url_or_alias: &str, - block: Option<u64>, -) -> Result { +fn create_fork(ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option<u64>) -> Result { let fork = create_fork_request(ccx, url_or_alias, block)?; let id = ccx.ecx.db.create_fork(fork)?; Ok(id.abi_encode()) } /// Creates and then also selects the new fork at the given transaction -fn create_select_fork_at_transaction<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, +fn create_select_fork_at_transaction( + ccx: &mut CheatsCtxt, url_or_alias: &str, transaction: &B256, ) -> Result { @@ -335,8 +319,8 @@ fn create_select_fork_at_transaction<DB: DatabaseExt>( } /// Creates a new fork at the given transaction -fn create_fork_at_transaction<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, +fn create_fork_at_transaction( + ccx: &mut CheatsCtxt, url_or_alias: &str, transaction: &B256, ) -> Result { @@ -346,8 +330,8 @@ fn create_fork_at_transaction<DB: DatabaseExt>( } /// Creates the request object for a new fork request -fn create_fork_request<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, +fn create_fork_request( + ccx: &mut CheatsCtxt, url_or_alias: &str, block: Option<u64>, ) -> Result<CreateFork> { @@ -380,7 +364,7 @@ fn check_broadcast(state: &Cheatcodes) -> Result<()> { // Applies to create, select and roll forks actions. // https://github.com/foundry-rs/foundry/issues/8004 #[inline] -fn persist_caller<DB: DatabaseExt>(ccx: &mut CheatsCtxt<DB>) { +fn persist_caller(ccx: &mut CheatsCtxt) { ccx.ecx.db.add_persistent_account(ccx.caller); } diff --git a/crates/cheatcodes/src/evm/mock.rs b/crates/cheatcodes/src/evm/mock.rs index 01a02c5c21c9d..ab858c612da69 100644 --- a/crates/cheatcodes/src/evm/mock.rs +++ b/crates/cheatcodes/src/evm/mock.rs @@ -1,6 +1,6 @@ -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; +use crate::{inspector::InnerEcx, Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Address, Bytes, U256}; -use revm::{interpreter::InstructionResult, primitives::Bytecode, InnerEvmContext}; +use revm::{interpreter::InstructionResult, primitives::Bytecode}; use std::cmp::Ordering; /// Mocked call data. @@ -47,7 +47,7 @@ impl Cheatcode for clearMockedCallsCall { } impl Cheatcode for mockCall_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, returnData } = self; let _ = make_acc_non_empty(callee, ccx.ecx)?; @@ -57,7 +57,7 @@ impl Cheatcode for mockCall_0Call { } impl Cheatcode for mockCall_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, returnData } = self; ccx.ecx.load_account(*callee)?; mock_call(ccx.state, callee, data, Some(msgValue), returnData, InstructionResult::Return); @@ -66,7 +66,7 @@ impl Cheatcode for mockCall_1Call { } impl Cheatcode for mockCallRevert_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx.ecx)?; @@ -76,7 +76,7 @@ impl Cheatcode for mockCallRevert_0Call { } impl Cheatcode for mockCallRevert_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { callee, msgValue, data, revertData } = self; let _ = make_acc_non_empty(callee, ccx.ecx)?; @@ -111,7 +111,7 @@ fn mock_call( // Etches a single byte onto the account if it is empty to circumvent the `extcodesize` // check Solidity might perform. -fn make_acc_non_empty<DB: DatabaseExt>(callee: &Address, ecx: &mut InnerEvmContext<DB>) -> Result { +fn make_acc_non_empty(callee: &Address, ecx: InnerEcx) -> Result { let acc = ecx.load_account(*callee)?; let empty_bytecode = acc.info.code.as_ref().map_or(true, Bytecode::is_empty); diff --git a/crates/cheatcodes/src/evm/prank.rs b/crates/cheatcodes/src/evm/prank.rs index fe5418b3157f8..a310e28e515bc 100644 --- a/crates/cheatcodes/src/evm/prank.rs +++ b/crates/cheatcodes/src/evm/prank.rs @@ -1,4 +1,4 @@ -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::Address; /// Prank information. @@ -45,28 +45,28 @@ impl Prank { } impl Cheatcode for prank_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, true) } } impl Cheatcode for startPrank_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender } = self; prank(ccx, msgSender, None, false) } } impl Cheatcode for prank_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), true) } } impl Cheatcode for startPrank_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { msgSender, txOrigin } = self; prank(ccx, msgSender, Some(txOrigin), false) } @@ -80,8 +80,8 @@ impl Cheatcode for stopPrankCall { } } -fn prank<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, +fn prank( + ccx: &mut CheatsCtxt, new_caller: &Address, new_origin: Option<&Address>, single_call: bool, diff --git a/crates/cheatcodes/src/fs.rs b/crates/cheatcodes/src/fs.rs index 4185b2d799976..f36c8d6fe8f8c 100644 --- a/crates/cheatcodes/src/fs.rs +++ b/crates/cheatcodes/src/fs.rs @@ -9,7 +9,6 @@ use alloy_sol_types::SolValue; use dialoguer::{Input, Password}; use foundry_common::fs; use foundry_config::fs_permissions::FsAccessKind; -use foundry_evm_core::backend::DatabaseExt; use revm::interpreter::CreateInputs; use semver::Version; use std::{ @@ -293,11 +292,7 @@ impl Cheatcode for getDeployedCodeCall { } impl Cheatcode for deployCode_0Call { - fn apply_full<DB: DatabaseExt, E: CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let Self { artifactPath: path } = self; let bytecode = get_artifact_code(ccx.state, path, false)?; let address = executor @@ -319,11 +314,7 @@ impl Cheatcode for deployCode_0Call { } impl Cheatcode for deployCode_1Call { - fn apply_full<DB: DatabaseExt, E: CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let Self { artifactPath: path, constructorArgs } = self; let mut bytecode = get_artifact_code(ccx.state, path, false)?.to_vec(); bytecode.extend_from_slice(constructorArgs); diff --git a/crates/cheatcodes/src/inspector.rs b/crates/cheatcodes/src/inspector.rs index 5ad5a7f74e439..8c79348f4d7aa 100644 --- a/crates/cheatcodes/src/inspector.rs +++ b/crates/cheatcodes/src/inspector.rs @@ -5,7 +5,7 @@ use crate::{ mapping::{self, MappingSlots}, mock::{MockCallDataContext, MockCallReturnData}, prank::Prank, - DealRecord, RecordAccess, + DealRecord, GasRecord, RecordAccess, }, inspector::utils::CommonCreateInput, script::{Broadcast, ScriptWallets}, @@ -17,8 +17,8 @@ use crate::{ }, }, utils::IgnoredTraces, - CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, Vm, - Vm::AccountAccess, + CheatsConfig, CheatsCtxt, DynCheatcode, Error, Result, + Vm::{self, AccountAccess}, }; use alloy_primitives::{ hex, @@ -31,7 +31,7 @@ use foundry_common::{evm::Breakpoints, TransactionMaybeSigned, SELECTOR_LEN}; use foundry_config::Config; use foundry_evm_core::{ abi::Vm::stopExpectSafeMemoryCall, - backend::{DatabaseExt, RevertDiagnostic}, + backend::{DatabaseError, DatabaseExt, RevertDiagnostic}, constants::{CHEATCODE_ADDRESS, HARDHAT_CONSOLE_ADDRESS, MAGIC_ASSUME}, utils::new_evm_with_existing_context, InspectorExt, @@ -62,6 +62,9 @@ use std::{ mod utils; +pub type Ecx<'a, 'b, 'c> = &'a mut EvmContext<&'b mut (dyn DatabaseExt + 'c)>; +pub type InnerEcx<'a, 'b, 'c> = &'a mut InnerEvmContext<&'b mut (dyn DatabaseExt + 'c)>; + /// Helper trait for obtaining complete [revm::Inspector] instance from mutable reference to /// [Cheatcodes]. /// @@ -70,71 +73,30 @@ mod utils; pub trait CheatcodesExecutor { /// Core trait method accepting mutable reference to [Cheatcodes] and returning /// [revm::Inspector]. - fn get_inspector<'a, DB: DatabaseExt>( - &'a mut self, - cheats: &'a mut Cheatcodes, - ) -> impl InspectorExt<DB> + 'a; - - /// Constructs [revm::Evm] and runs a given closure with it. - fn with_evm<DB: DatabaseExt, F, O>( - &mut self, - ccx: &mut CheatsCtxt<DB>, - f: F, - ) -> Result<O, EVMError<DB::Error>> - where - F: for<'a, 'b> FnOnce( - &mut revm::Evm< - '_, - &'b mut dyn InspectorExt<&'a mut dyn DatabaseExt>, - &'a mut dyn DatabaseExt, - >, - ) -> Result<O, EVMError<DB::Error>>, - { - let mut inspector = self.get_inspector(ccx.state); - let error = std::mem::replace(&mut ccx.ecx.error, Ok(())); - let l1_block_info = std::mem::take(&mut ccx.ecx.l1_block_info); - - let inner = revm::InnerEvmContext { - env: ccx.ecx.env.clone(), - journaled_state: std::mem::replace( - &mut ccx.ecx.journaled_state, - revm::JournaledState::new(Default::default(), Default::default()), - ), - db: &mut ccx.ecx.db as &mut dyn DatabaseExt, - error, - l1_block_info, - }; - - let mut evm = new_evm_with_existing_context(inner, &mut inspector as _); - - let res = f(&mut evm)?; - - ccx.ecx.journaled_state = evm.context.evm.inner.journaled_state; - ccx.ecx.env = evm.context.evm.inner.env; - ccx.ecx.l1_block_info = evm.context.evm.inner.l1_block_info; - ccx.ecx.error = evm.context.evm.inner.error; - - Ok(res) - } + fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box<dyn InspectorExt + 'a>; /// Obtains [revm::Evm] instance and executes the given CREATE frame. - fn exec_create<DB: DatabaseExt>( + fn exec_create( &mut self, inputs: CreateInputs, - ccx: &mut CheatsCtxt<DB>, - ) -> Result<CreateOutcome, EVMError<DB::Error>> { - self.with_evm(ccx, |evm| { + ccx: &mut CheatsCtxt, + ) -> Result<CreateOutcome, EVMError<DatabaseError>> { + with_evm(self, ccx, |evm| { evm.context.evm.inner.journaled_state.depth += 1; // Handle EOF bytecode - let first_frame_or_result = if evm.handler.cfg.spec_id.is_enabled_in(SpecId::PRAGUE_EOF) - && inputs.scheme == CreateScheme::Create && inputs.init_code.starts_with(&EOF_MAGIC_BYTES) + let first_frame_or_result = if evm.handler.cfg.spec_id.is_enabled_in(SpecId::PRAGUE_EOF) && + inputs.scheme == CreateScheme::Create && + inputs.init_code.starts_with(&EOF_MAGIC_BYTES) { evm.handler.execution().eofcreate( &mut evm.context, - Box::new(EOFCreateInputs::new(inputs.caller, inputs.value, inputs.gas_limit, EOFCreateKind::Tx { - initdata: inputs.init_code, - })), + Box::new(EOFCreateInputs::new( + inputs.caller, + inputs.value, + inputs.gas_limit, + EOFCreateKind::Tx { initdata: inputs.init_code }, + )), )? } else { evm.handler.execution().create(&mut evm.context, Box::new(inputs))? @@ -158,8 +120,8 @@ pub trait CheatcodesExecutor { }) } - fn console_log<DB: DatabaseExt>(&mut self, ccx: &mut CheatsCtxt<DB>, message: String) { - self.get_inspector::<DB>(ccx.state).console_log(message); + fn console_log(&mut self, ccx: &mut CheatsCtxt, message: String) { + self.get_inspector(ccx.state).console_log(message); } /// Returns a mutable reference to the tracing inspector if it is available. @@ -168,17 +130,53 @@ pub trait CheatcodesExecutor { } } +/// Constructs [revm::Evm] and runs a given closure with it. +fn with_evm<E, F, O>( + executor: &mut E, + ccx: &mut CheatsCtxt, + f: F, +) -> Result<O, EVMError<DatabaseError>> +where + E: CheatcodesExecutor + ?Sized, + F: for<'a, 'b> FnOnce( + &mut revm::Evm<'_, &'b mut dyn InspectorExt, &'a mut dyn DatabaseExt>, + ) -> Result<O, EVMError<DatabaseError>>, +{ + let mut inspector = executor.get_inspector(ccx.state); + let error = std::mem::replace(&mut ccx.ecx.error, Ok(())); + let l1_block_info = std::mem::take(&mut ccx.ecx.l1_block_info); + + let inner = revm::InnerEvmContext { + env: ccx.ecx.env.clone(), + journaled_state: std::mem::replace( + &mut ccx.ecx.journaled_state, + revm::JournaledState::new(Default::default(), Default::default()), + ), + db: &mut ccx.ecx.db as &mut dyn DatabaseExt, + error, + l1_block_info, + }; + + let mut evm = new_evm_with_existing_context(inner, &mut *inspector); + + let res = f(&mut evm)?; + + ccx.ecx.journaled_state = evm.context.evm.inner.journaled_state; + ccx.ecx.env = evm.context.evm.inner.env; + ccx.ecx.l1_block_info = evm.context.evm.inner.l1_block_info; + ccx.ecx.error = evm.context.evm.inner.error; + + Ok(res) +} + /// Basic implementation of [CheatcodesExecutor] that simply returns the [Cheatcodes] instance as an /// inspector. #[derive(Debug, Default, Clone, Copy)] struct TransparentCheatcodesExecutor; impl CheatcodesExecutor for TransparentCheatcodesExecutor { - fn get_inspector<'a, DB: DatabaseExt>( - &'a mut self, - cheats: &'a mut Cheatcodes, - ) -> impl InspectorExt<DB> + 'a { - cheats + fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box<dyn InspectorExt + 'a> { + Box::new(cheats) } } @@ -240,15 +238,35 @@ pub struct GasMetering { pub touched: bool, /// True if gas metering should be reset to frame limit. pub reset: bool, - /// Stores frames paused gas. + /// Stores paused gas frames. pub paused_frames: Vec<Gas>, + /// The group and name of the active snapshot. + pub active_gas_snapshot: Option<(String, String)>, + /// Cache of the amount of gas used in previous call. /// This is used by the `lastCallGas` cheatcode. pub last_call_gas: Option<crate::Vm::Gas>, + + /// True if gas recording is enabled. + pub recording: bool, + /// The gas used in the last frame. + pub last_gas_used: u64, + /// Gas records for the active snapshots. + pub gas_records: Vec<GasRecord>, } impl GasMetering { + /// Start the gas recording. + pub fn start(&mut self) { + self.recording = true; + } + + /// Stop the gas recording. + pub fn stop(&mut self) { + self.recording = false; + } + /// Resume paused gas metering. pub fn resume(&mut self) { if self.paused { @@ -294,13 +312,7 @@ impl ArbitraryStorage { /// Saves arbitrary storage value for a given address: /// - store value in changed values cache. /// - update account's storage with given value. - pub fn save<DB: DatabaseExt>( - &mut self, - ecx: &mut InnerEvmContext<DB>, - address: Address, - slot: U256, - data: U256, - ) { + pub fn save(&mut self, ecx: InnerEcx, address: Address, slot: U256, data: U256) { self.values.get_mut(&address).expect("missing arbitrary address entry").insert(slot, data); if let Ok(mut account) = ecx.load_account(address) { account.storage.insert(slot, EvmStorageSlot::new(data)); @@ -312,13 +324,7 @@ impl ArbitraryStorage { /// existing value. /// - if no value was yet generated for given slot, then save new value in cache and update both /// source and target storages. - pub fn copy<DB: DatabaseExt>( - &mut self, - ecx: &mut InnerEvmContext<DB>, - target: Address, - slot: U256, - new_value: U256, - ) -> U256 { + pub fn copy(&mut self, ecx: InnerEcx, target: Address, slot: U256, new_value: U256) -> U256 { let source = self.copies.get(&target).expect("missing arbitrary copy target entry"); let storage_cache = self.values.get_mut(source).expect("missing arbitrary source storage"); let value = match storage_cache.get(&slot) { @@ -446,6 +452,10 @@ pub struct Cheatcodes { /// Gas metering state. pub gas_metering: GasMetering, + /// Contains gas snapshots made over the course of a test suite. + // **Note**: both must a BTreeMap to ensure the order of the keys is deterministic. + pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>, + /// Mapping slots. pub mapping_slots: Option<AddressHashMap<MappingSlots>>, @@ -506,6 +516,7 @@ impl Cheatcodes { serialized_jsons: Default::default(), eth_deals: Default::default(), gas_metering: Default::default(), + gas_snapshots: Default::default(), mapping_slots: Default::default(), pc: Default::default(), breakpoints: Default::default(), @@ -522,11 +533,11 @@ impl Cheatcodes { } /// Decodes the input data and applies the cheatcode. - fn apply_cheatcode<DB: DatabaseExt, E: CheatcodesExecutor>( + fn apply_cheatcode( &mut self, - ecx: &mut EvmContext<DB>, + ecx: Ecx, call: &CallInputs, - executor: &mut E, + executor: &mut dyn CheatcodesExecutor, ) -> Result { // decode the cheatcode call let decoded = Vm::VmCalls::abi_decode(&call.input, false).map_err(|e| { @@ -565,12 +576,7 @@ impl Cheatcodes { /// /// There may be cheatcodes in the constructor of the new contract, in order to allow them /// automatically we need to determine the new address. - fn allow_cheatcodes_on_create<DB: DatabaseExt>( - &self, - ecx: &mut InnerEvmContext<DB>, - caller: Address, - created_address: Address, - ) { + fn allow_cheatcodes_on_create(&self, ecx: InnerEcx, caller: Address, created_address: Address) { if ecx.journaled_state.depth <= 1 || ecx.db.has_cheatcode_access(&caller) { ecx.db.allow_cheatcode_access(created_address); } @@ -580,7 +586,7 @@ impl Cheatcodes { /// /// Cleanup any previously applied cheatcodes that altered the state in such a way that revm's /// revert would run into issues. - pub fn on_revert<DB: DatabaseExt>(&mut self, ecx: &mut EvmContext<DB>) { + pub fn on_revert(&mut self, ecx: Ecx) { trace!(deals=?self.eth_deals.len(), "rolling back deals"); // Delay revert clean up until expected revert is handled, if set. @@ -604,14 +610,9 @@ impl Cheatcodes { } // common create functionality for both legacy and EOF. - fn create_common<DB, Input>( - &mut self, - ecx: &mut EvmContext<DB>, - mut input: Input, - ) -> Option<CreateOutcome> + fn create_common<Input>(&mut self, ecx: Ecx, mut input: Input) -> Option<CreateOutcome> where - DB: DatabaseExt, - Input: CommonCreateInput<DB>, + Input: CommonCreateInput, { let ecx = &mut ecx.inner; let gas = Gas::new(input.gas_limit()); @@ -664,11 +665,7 @@ impl Cheatcodes { value: Some(input.value()), input: TransactionInput::new(input.init_code()), nonce: Some(account.info.nonce), - gas: if is_fixed_gas_limit { - Some(input.gas_limit() as u128) - } else { - None - }, + gas: if is_fixed_gas_limit { Some(input.gas_limit()) } else { None }, ..Default::default() } .into(), @@ -708,14 +705,8 @@ impl Cheatcodes { } // common create_end functionality for both legacy and EOF. - fn create_end_common<DB>( - &mut self, - ecx: &mut EvmContext<DB>, - mut outcome: CreateOutcome, - ) -> CreateOutcome - where - DB: DatabaseExt, - { + fn create_end_common(&mut self, ecx: Ecx, mut outcome: CreateOutcome) -> CreateOutcome +where { let ecx = &mut ecx.inner; // Clean up pranks @@ -822,9 +813,9 @@ impl Cheatcodes { outcome } - pub fn call_with_executor<DB: DatabaseExt>( + pub fn call_with_executor( &mut self, - ecx: &mut EvmContext<DB>, + ecx: Ecx, call: &mut CallInputs, executor: &mut impl CheatcodesExecutor, ) -> Option<CallOutcome> { @@ -1003,11 +994,7 @@ impl Cheatcodes { value: call.transfer_value(), input: TransactionInput::new(call.input.clone()), nonce: Some(account.info.nonce), - gas: if is_fixed_gas_limit { - Some(call.gas_limit as u128) - } else { - None - }, + gas: if is_fixed_gas_limit { Some(call.gas_limit) } else { None }, ..Default::default() } .into(), @@ -1122,9 +1109,9 @@ impl Cheatcodes { } } -impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes { +impl Inspector<&mut dyn DatabaseExt> for Cheatcodes { #[inline] - fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { // When the first interpreter is initialized we've circumvented the balance and gas checks, // so we apply our actual block data with the correct fees and all. if let Some(block) = self.block.take() { @@ -1141,7 +1128,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes { } #[inline] - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { self.pc = interpreter.program_counter(); // `pauseGasMetering`: pause / resume interpreter gas. @@ -1173,10 +1160,15 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes { if let Some(mapping_slots) = &mut self.mapping_slots { mapping::step(mapping_slots, interpreter); } + + // `snapshotGas*`: take a snapshot of the current gas. + if self.gas_metering.recording { + self.meter_gas_record(interpreter, ecx); + } } #[inline] - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn step_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { if self.gas_metering.paused { self.meter_gas_end(interpreter); } @@ -1191,7 +1183,7 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes { } } - fn log(&mut self, interpreter: &mut Interpreter, _ecx: &mut EvmContext<DB>, log: &Log) { + fn log(&mut self, interpreter: &mut Interpreter, _ecx: Ecx, log: &Log) { if !self.expected_emits.is_empty() { expect::handle_expect_emit(self, log, interpreter); } @@ -1206,16 +1198,11 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes { } } - fn call(&mut self, ecx: &mut EvmContext<DB>, inputs: &mut CallInputs) -> Option<CallOutcome> { + fn call(&mut self, ecx: Ecx, inputs: &mut CallInputs) -> Option<CallOutcome> { Self::call_with_executor(self, ecx, inputs, &mut TransparentCheatcodesExecutor) } - fn call_end( - &mut self, - ecx: &mut EvmContext<DB>, - call: &CallInputs, - mut outcome: CallOutcome, - ) -> CallOutcome { + fn call_end(&mut self, ecx: Ecx, call: &CallInputs, mut outcome: CallOutcome) -> CallOutcome { let ecx = &mut ecx.inner; let cheatcode_call = call.target_address == CHEATCODE_ADDRESS || call.target_address == HARDHAT_CONSOLE_ADDRESS; @@ -1511,34 +1498,26 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes { outcome } - fn create( - &mut self, - ecx: &mut EvmContext<DB>, - call: &mut CreateInputs, - ) -> Option<CreateOutcome> { + fn create(&mut self, ecx: Ecx, call: &mut CreateInputs) -> Option<CreateOutcome> { self.create_common(ecx, call) } fn create_end( &mut self, - ecx: &mut EvmContext<DB>, + ecx: Ecx, _call: &CreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { self.create_end_common(ecx, outcome) } - fn eofcreate( - &mut self, - ecx: &mut EvmContext<DB>, - call: &mut EOFCreateInputs, - ) -> Option<CreateOutcome> { + fn eofcreate(&mut self, ecx: Ecx, call: &mut EOFCreateInputs) -> Option<CreateOutcome> { self.create_common(ecx, call) } fn eofcreate_end( &mut self, - ecx: &mut EvmContext<DB>, + ecx: Ecx, _call: &EOFCreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { @@ -1546,12 +1525,8 @@ impl<DB: DatabaseExt> Inspector<DB> for Cheatcodes { } } -impl<DB: DatabaseExt> InspectorExt<DB> for Cheatcodes { - fn should_use_create2_factory( - &mut self, - ecx: &mut EvmContext<DB>, - inputs: &mut CreateInputs, - ) -> bool { +impl InspectorExt for Cheatcodes { + fn should_use_create2_factory(&mut self, ecx: Ecx, inputs: &mut CreateInputs) -> bool { if let CreateScheme::Create2 { .. } = inputs.scheme { let target_depth = if let Some(prank) = &self.prank { prank.depth @@ -1581,6 +1556,27 @@ impl Cheatcodes { } } + #[cold] + fn meter_gas_record(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { + if matches!(interpreter.instruction_result, InstructionResult::Continue) { + self.gas_metering.gas_records.iter_mut().for_each(|record| { + if ecx.journaled_state.depth() == record.depth { + // Skip the first opcode of the first call frame as it includes the gas cost of + // creating the snapshot. + if self.gas_metering.last_gas_used != 0 { + let gas_diff = + interpreter.gas.spent().saturating_sub(self.gas_metering.last_gas_used); + record.gas_used = record.gas_used.saturating_add(gas_diff); + } + + // Update `last_gas_used` to the current spent gas for the next iteration to + // compare against. + self.gas_metering.last_gas_used = interpreter.gas.spent(); + } + }); + } + } + #[cold] fn meter_gas_end(&mut self, interpreter: &mut Interpreter) { // Remove recorded gas if we exit frame. @@ -1617,11 +1613,7 @@ impl Cheatcodes { /// cache) from mapped source address to the target address. /// - generates arbitrary value and saves it in target address storage. #[cold] - fn arbitrary_storage_end<DB: DatabaseExt>( - &mut self, - interpreter: &mut Interpreter, - ecx: &mut EvmContext<DB>, - ) { + fn arbitrary_storage_end(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { let (key, target_address) = if interpreter.current_opcode() == op::SLOAD { (try_or_return!(interpreter.stack().peek(0)), interpreter.contract().target_address) } else { @@ -1671,11 +1663,7 @@ impl Cheatcodes { } #[cold] - fn record_state_diffs<DB: DatabaseExt>( - &mut self, - interpreter: &mut Interpreter, - ecx: &mut EvmContext<DB>, - ) { + fn record_state_diffs(&mut self, interpreter: &mut Interpreter, ecx: Ecx) { let Some(account_accesses) = &mut self.recorded_account_diffs_stack else { return }; match interpreter.current_opcode() { op::SELFDESTRUCT => { @@ -2022,10 +2010,7 @@ fn disallowed_mem_write( // Determines if the gas limit on a given call was manually set in the script and should therefore // not be overwritten by later estimations -fn check_if_fixed_gas_limit<DB: DatabaseExt>( - ecx: &InnerEvmContext<DB>, - call_gas_limit: u64, -) -> bool { +fn check_if_fixed_gas_limit(ecx: InnerEcx, call_gas_limit: u64) -> bool { // If the gas limit was not set in the source code it is set to the estimated gas left at the // time of the call, which should be rather close to configured gas limit. // TODO: Find a way to reliably make this determination. @@ -2095,44 +2080,25 @@ fn append_storage_access( } /// Dispatches the cheatcode call to the appropriate function. -fn apply_dispatch<DB: DatabaseExt, E: CheatcodesExecutor>( +fn apply_dispatch( calls: &Vm::VmCalls, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, + ccx: &mut CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, ) -> Result { - // TODO: Replace with `<dyn Cheatcode>::apply_full` once it's object-safe. - macro_rules! dispatch { - ($($variant:ident),*) => { - match calls { - $(Vm::VmCalls::$variant(cheat) => crate::Cheatcode::apply_full(cheat, ccx, executor),)* - } - }; - } - let cheat = calls_as_dyn_cheatcode(calls); + + let _guard = debug_span!(target: "cheatcodes", "apply", id = %cheat.id()).entered(); + trace!(target: "cheatcodes", cheat = ?cheat.as_debug(), "applying"); + if let spec::Status::Deprecated(replacement) = *cheat.status() { ccx.state.deprecated.insert(cheat.signature(), replacement); } - let _guard = trace_span_and_call(cheat); - let mut result = vm_calls!(dispatch); - fill_and_trace_return(cheat, &mut result); - result -} - -/// Helper function to check if frame execution will exit. -fn will_exit(ir: InstructionResult) -> bool { - !matches!(ir, InstructionResult::Continue | InstructionResult::CallOrCreate) -} -fn trace_span_and_call(cheat: &dyn DynCheatcode) -> tracing::span::EnteredSpan { - let span = debug_span!(target: "cheatcodes", "apply", id = %cheat.id()); - let entered = span.entered(); - trace!(target: "cheatcodes", cheat = ?cheat.as_debug(), "applying"); - entered -} + // Apply the cheatcode. + let mut result = cheat.dyn_apply(ccx, executor); -fn fill_and_trace_return(cheat: &dyn DynCheatcode, result: &mut Result) { - if let Err(e) = result { + // Format the error message to include the cheatcode name. + if let Err(e) = &mut result { if e.is_str() { let name = cheat.name(); // Skip showing the cheatcode name for: @@ -2143,13 +2109,16 @@ fn fill_and_trace_return(cheat: &dyn DynCheatcode, result: &mut Result) { } } } + trace!( target: "cheatcodes", - return = %match result { + return = %match &result { Ok(b) => hex::encode(b), Err(e) => e.to_string(), } ); + + result } fn calls_as_dyn_cheatcode(calls: &Vm::VmCalls) -> &dyn DynCheatcode { @@ -2162,3 +2131,8 @@ fn calls_as_dyn_cheatcode(calls: &Vm::VmCalls) -> &dyn DynCheatcode { } vm_calls!(as_dyn) } + +/// Helper function to check if frame execution will exit. +fn will_exit(ir: InstructionResult) -> bool { + !matches!(ir, InstructionResult::Continue | InstructionResult::CallOrCreate) +} diff --git a/crates/cheatcodes/src/inspector/utils.rs b/crates/cheatcodes/src/inspector/utils.rs index dfccd4b55552c..a0d7820aa3e27 100644 --- a/crates/cheatcodes/src/inspector/utils.rs +++ b/crates/cheatcodes/src/inspector/utils.rs @@ -1,13 +1,10 @@ +use super::InnerEcx; use crate::inspector::Cheatcodes; use alloy_primitives::{Address, Bytes, U256}; -use foundry_evm_core::backend::DatabaseExt; -use revm::{ - interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}, - InnerEvmContext, -}; +use revm::interpreter::{CreateInputs, CreateScheme, EOFCreateInputs, EOFCreateKind}; /// Common behaviour of legacy and EOF create inputs. -pub(crate) trait CommonCreateInput<DB: DatabaseExt> { +pub(crate) trait CommonCreateInput { fn caller(&self) -> Address; fn gas_limit(&self) -> u64; fn value(&self) -> U256; @@ -15,15 +12,11 @@ pub(crate) trait CommonCreateInput<DB: DatabaseExt> { fn scheme(&self) -> Option<CreateScheme>; fn set_caller(&mut self, caller: Address); fn log_debug(&self, cheatcode: &mut Cheatcodes, scheme: &CreateScheme); - fn allow_cheatcodes( - &self, - cheatcodes: &mut Cheatcodes, - ecx: &mut InnerEvmContext<DB>, - ) -> Address; + fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: InnerEcx) -> Address; fn computed_created_address(&self) -> Option<Address>; } -impl<DB: DatabaseExt> CommonCreateInput<DB> for &mut CreateInputs { +impl CommonCreateInput for &mut CreateInputs { fn caller(&self) -> Address { self.caller } @@ -49,11 +42,7 @@ impl<DB: DatabaseExt> CommonCreateInput<DB> for &mut CreateInputs { }; debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable {kind}"); } - fn allow_cheatcodes( - &self, - cheatcodes: &mut Cheatcodes, - ecx: &mut InnerEvmContext<DB>, - ) -> Address { + fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: InnerEcx) -> Address { let old_nonce = ecx .journaled_state .state @@ -69,7 +58,7 @@ impl<DB: DatabaseExt> CommonCreateInput<DB> for &mut CreateInputs { } } -impl<DB: DatabaseExt> CommonCreateInput<DB> for &mut EOFCreateInputs { +impl CommonCreateInput for &mut EOFCreateInputs { fn caller(&self) -> Address { self.caller } @@ -94,13 +83,9 @@ impl<DB: DatabaseExt> CommonCreateInput<DB> for &mut EOFCreateInputs { fn log_debug(&self, cheatcode: &mut Cheatcodes, _scheme: &CreateScheme) { debug!(target: "cheatcodes", tx=?cheatcode.broadcastable_transactions.back().unwrap(), "broadcastable eofcreate"); } - fn allow_cheatcodes( - &self, - cheatcodes: &mut Cheatcodes, - ecx: &mut InnerEvmContext<DB>, - ) -> Address { + fn allow_cheatcodes(&self, cheatcodes: &mut Cheatcodes, ecx: InnerEcx) -> Address { let created_address = - <&mut EOFCreateInputs as CommonCreateInput<DB>>::computed_created_address(self) + <&mut EOFCreateInputs as CommonCreateInput>::computed_created_address(self) .unwrap_or_default(); cheatcodes.allow_cheatcodes_on_create(ecx, self.caller, created_address); created_address diff --git a/crates/cheatcodes/src/lib.rs b/crates/cheatcodes/src/lib.rs index 50bd54701919d..a257289916cd7 100644 --- a/crates/cheatcodes/src/lib.rs +++ b/crates/cheatcodes/src/lib.rs @@ -70,7 +70,7 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the EVM data. #[inline(always)] - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { self.apply(ccx.state) } @@ -78,58 +78,69 @@ pub(crate) trait Cheatcode: CheatcodeDef + DynCheatcode { /// /// Implement this function if you need access to the executor. #[inline(always)] - fn apply_full<DB: DatabaseExt, E: CheatcodesExecutor>( - &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, - ) -> Result { + fn apply_full(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { let _ = executor; self.apply_stateful(ccx) } } pub(crate) trait DynCheatcode { - fn name(&self) -> &'static str; - fn id(&self) -> &'static str; - fn signature(&self) -> &'static str; - fn status(&self) -> &Status<'static>; - fn as_debug(&self) -> &dyn std::fmt::Debug; -} + fn cheatcode(&self) -> &'static spec::Cheatcode<'static>; -impl<T: Cheatcode> DynCheatcode for T { fn name(&self) -> &'static str { - T::CHEATCODE.func.signature.split('(').next().unwrap() + self.cheatcode().func.signature.split('(').next().unwrap() } + fn id(&self) -> &'static str { - T::CHEATCODE.func.id + self.cheatcode().func.id } + fn signature(&self) -> &'static str { - T::CHEATCODE.func.signature + self.cheatcode().func.signature } + fn status(&self) -> &Status<'static> { - &T::CHEATCODE.status + &self.cheatcode().status } + + fn as_debug(&self) -> &dyn std::fmt::Debug; + + fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result; +} + +impl<T: Cheatcode> DynCheatcode for T { + #[inline] + fn cheatcode(&self) -> &'static spec::Cheatcode<'static> { + Self::CHEATCODE + } + + #[inline] fn as_debug(&self) -> &dyn std::fmt::Debug { self } + + #[inline] + fn dyn_apply(&self, ccx: &mut CheatsCtxt, executor: &mut dyn CheatcodesExecutor) -> Result { + self.apply_full(ccx, executor) + } } /// The cheatcode context, used in `Cheatcode`. -pub struct CheatsCtxt<'cheats, 'evm, DB: DatabaseExt> { +pub struct CheatsCtxt<'cheats, 'evm, 'db, 'db2> { /// The cheatcodes inspector state. pub(crate) state: &'cheats mut Cheatcodes, /// The EVM data. - pub(crate) ecx: &'evm mut InnerEvmContext<DB>, + pub(crate) ecx: &'evm mut InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>, /// The precompiles context. - pub(crate) precompiles: &'evm mut ContextPrecompiles<DB>, + pub(crate) precompiles: &'evm mut ContextPrecompiles<&'db mut (dyn DatabaseExt + 'db2)>, /// The original `msg.sender`. pub(crate) caller: Address, /// Gas limit of the current cheatcode call. pub(crate) gas_limit: u64, } -impl<'cheats, 'evm, DB: DatabaseExt> std::ops::Deref for CheatsCtxt<'cheats, 'evm, DB> { - type Target = InnerEvmContext<DB>; +impl<'db, 'db2> std::ops::Deref for CheatsCtxt<'_, '_, 'db, 'db2> { + type Target = InnerEvmContext<&'db mut (dyn DatabaseExt + 'db2)>; #[inline(always)] fn deref(&self) -> &Self::Target { @@ -137,14 +148,14 @@ impl<'cheats, 'evm, DB: DatabaseExt> std::ops::Deref for CheatsCtxt<'cheats, 'ev } } -impl<'cheats, 'evm, DB: DatabaseExt> std::ops::DerefMut for CheatsCtxt<'cheats, 'evm, DB> { +impl std::ops::DerefMut for CheatsCtxt<'_, '_, '_, '_> { #[inline(always)] fn deref_mut(&mut self) -> &mut Self::Target { &mut *self.ecx } } -impl<'cheats, 'evm, DB: DatabaseExt> CheatsCtxt<'cheats, 'evm, DB> { +impl CheatsCtxt<'_, '_, '_, '_> { #[inline] pub(crate) fn is_precompile(&self, address: &Address) -> bool { self.precompiles.contains(address) diff --git a/crates/cheatcodes/src/script.rs b/crates/cheatcodes/src/script.rs index 82eef2354994a..93d5aaaf8d349 100644 --- a/crates/cheatcodes/src/script.rs +++ b/crates/cheatcodes/src/script.rs @@ -1,6 +1,6 @@ //! Implementations of [`Scripting`](spec::Group::Scripting) cheatcodes. -use crate::{Cheatcode, CheatsCtxt, DatabaseExt, Result, Vm::*}; +use crate::{Cheatcode, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{Address, B256, U256}; use alloy_signer_local::PrivateKeySigner; use foundry_wallets::{multi_wallet::MultiWallet, WalletSigner}; @@ -8,49 +8,49 @@ use parking_lot::Mutex; use std::sync::Arc; impl Cheatcode for broadcast_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; broadcast(ccx, None, true) } } impl Cheatcode for broadcast_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), true) } } impl Cheatcode for broadcast_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, true) } } impl Cheatcode for startBroadcast_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; broadcast(ccx, None, false) } } impl Cheatcode for startBroadcast_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { signer } = self; broadcast(ccx, Some(signer), false) } } impl Cheatcode for startBroadcast_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { privateKey } = self; broadcast_key(ccx, privateKey, false) } } impl Cheatcode for stopBroadcastCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; let Some(broadcast) = ccx.state.broadcast.take() else { bail!("no broadcast in progress to stop"); @@ -123,11 +123,7 @@ impl ScriptWallets { } /// Sets up broadcasting from a script using `new_origin` as the sender. -fn broadcast<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, - new_origin: Option<&Address>, - single_call: bool, -) -> Result { +fn broadcast(ccx: &mut CheatsCtxt, new_origin: Option<&Address>, single_call: bool) -> Result { ensure!( ccx.state.prank.is_none(), "you have an active prank; broadcasting and pranks are not compatible" @@ -166,11 +162,7 @@ fn broadcast<DB: DatabaseExt>( /// Sets up broadcasting from a script with the sender derived from `private_key`. /// Adds this private key to `state`'s `script_wallets` vector to later be used for signing /// if broadcast is successful. -fn broadcast_key<DB: DatabaseExt>( - ccx: &mut CheatsCtxt<DB>, - private_key: &U256, - single_call: bool, -) -> Result { +fn broadcast_key(ccx: &mut CheatsCtxt, private_key: &U256, single_call: bool) -> Result { let wallet = super::crypto::parse_wallet(private_key)?; let new_origin = wallet.address(); diff --git a/crates/cheatcodes/src/test.rs b/crates/cheatcodes/src/test.rs index cc91dba45b23d..bd723c9312ea2 100644 --- a/crates/cheatcodes/src/test.rs +++ b/crates/cheatcodes/src/test.rs @@ -1,26 +1,25 @@ //! Implementations of [`Testing`](spec::Group::Testing) cheatcodes. -use chrono::DateTime; -use std::env; - -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; use alloy_primitives::Address; use alloy_sol_types::SolValue; +use chrono::DateTime; use foundry_evm_core::constants::MAGIC_SKIP; +use std::env; pub(crate) mod assert; pub(crate) mod assume; pub(crate) mod expect; impl Cheatcode for breakpoint_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { char } = self; breakpoint(ccx.state, &ccx.caller, char, true) } } impl Cheatcode for breakpoint_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { char, value } = self; breakpoint(ccx.state, &ccx.caller, char, *value) } @@ -71,14 +70,14 @@ impl Cheatcode for sleepCall { } impl Cheatcode for skip_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { skipTest } = *self; skip_1Call { skipTest, reason: String::new() }.apply_stateful(ccx) } } impl Cheatcode for skip_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { skipTest, reason } = self; if *skipTest { // Skip should not work if called deeper than at test level. diff --git a/crates/cheatcodes/src/test/assert.rs b/crates/cheatcodes/src/test/assert.rs index 4ab97c031e5a8..5161716fae56c 100644 --- a/crates/cheatcodes/src/test/assert.rs +++ b/crates/cheatcodes/src/test/assert.rs @@ -2,7 +2,7 @@ use crate::{CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_primitives::{hex, I256, U256}; use foundry_evm_core::{ abi::{format_units_int, format_units_uint}, - backend::{DatabaseExt, GLOBAL_FAIL_SLOT}, + backend::GLOBAL_FAIL_SLOT, constants::CHEATCODE_ADDRESS, }; use itertools::Itertools; @@ -169,10 +169,10 @@ impl EqRelAssertionError<I256> { type ComparisonResult<'a, T> = Result<Vec<u8>, ComparisonAssertionError<'a, T>>; -fn handle_assertion_result<DB: DatabaseExt, E: CheatcodesExecutor, ERR>( +fn handle_assertion_result<ERR>( result: core::result::Result<Vec<u8>, ERR>, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, + ccx: &mut CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, error_formatter: impl Fn(&ERR) -> String, error_msg: Option<&str>, format_error: bool, @@ -224,10 +224,10 @@ macro_rules! impl_assertions { }; (@impl $no_error:ident, $with_error:ident, ($($arg:ident),*), $body:expr, $error_formatter:expr, $format_error:literal) => { impl crate::Cheatcode for $no_error { - fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>( + fn apply_full( &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, + ccx: &mut CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { $($arg),* } = self; handle_assertion_result($body, ccx, executor, $error_formatter, None, $format_error) @@ -235,10 +235,10 @@ macro_rules! impl_assertions { } impl crate::Cheatcode for $with_error { - fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>( + fn apply_full( &self, - ccx: &mut CheatsCtxt<DB>, - executor: &mut E, + ccx: &mut CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Self { $($arg),*, error} = self; handle_assertion_result($body, ccx, executor, $error_formatter, Some(error), $format_error) diff --git a/crates/cheatcodes/src/test/assume.rs b/crates/cheatcodes/src/test/assume.rs index e100eeb9d1c43..a0321b5a1cd38 100644 --- a/crates/cheatcodes/src/test/assume.rs +++ b/crates/cheatcodes/src/test/assume.rs @@ -1,5 +1,5 @@ use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result}; -use foundry_evm_core::{backend::DatabaseExt, constants::MAGIC_ASSUME}; +use foundry_evm_core::constants::MAGIC_ASSUME; use spec::Vm::{assumeCall, assumeNoRevertCall}; use std::fmt::Debug; @@ -21,7 +21,7 @@ impl Cheatcode for assumeCall { } impl Cheatcode for assumeNoRevertCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { ccx.state.assume_no_revert = Some(AssumeNoRevert { depth: ccx.ecx.journaled_state.depth() }); Ok(Default::default()) diff --git a/crates/cheatcodes/src/test/expect.rs b/crates/cheatcodes/src/test/expect.rs index f38776f94bfd5..7a58c7ab8aa81 100644 --- a/crates/cheatcodes/src/test/expect.rs +++ b/crates/cheatcodes/src/test/expect.rs @@ -1,4 +1,4 @@ -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, DatabaseExt, Error, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Error, Result, Vm::*}; use alloy_primitives::{address, hex, Address, Bytes, LogData as RawLog, U256}; use alloy_sol_types::{SolError, SolValue}; use foundry_common::ContractsByArtifact; @@ -210,7 +210,7 @@ impl Cheatcode for expectCallMinGas_1Call { } impl Cheatcode for expectEmit_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, @@ -223,7 +223,7 @@ impl Cheatcode for expectEmit_0Call { } impl Cheatcode for expectEmit_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, @@ -236,21 +236,21 @@ impl Cheatcode for expectEmit_1Call { } impl Cheatcode for expectEmit_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, false) } } impl Cheatcode for expectEmit_3Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { emitter } = *self; expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), false) } } impl Cheatcode for expectEmitAnonymous_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData } = *self; expect_emit( ccx.state, @@ -263,7 +263,7 @@ impl Cheatcode for expectEmitAnonymous_0Call { } impl Cheatcode for expectEmitAnonymous_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { checkTopic0, checkTopic1, checkTopic2, checkTopic3, checkData, emitter } = *self; expect_emit( ccx.state, @@ -276,28 +276,28 @@ impl Cheatcode for expectEmitAnonymous_1Call { } impl Cheatcode for expectEmitAnonymous_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], None, true) } } impl Cheatcode for expectEmitAnonymous_3Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { emitter } = *self; expect_emit(ccx.state, ccx.ecx.journaled_state.depth(), [true; 5], Some(emitter), true) } } impl Cheatcode for expectRevert_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), false, false, None) } } impl Cheatcode for expectRevert_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert( ccx.state, @@ -311,7 +311,7 @@ impl Cheatcode for expectRevert_1Call { } impl Cheatcode for expectRevert_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert( ccx.state, @@ -325,7 +325,7 @@ impl Cheatcode for expectRevert_2Call { } impl Cheatcode for expectRevert_3Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { reverter } = self; expect_revert( ccx.state, @@ -339,7 +339,7 @@ impl Cheatcode for expectRevert_3Call { } impl Cheatcode for expectRevert_4Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, @@ -353,7 +353,7 @@ impl Cheatcode for expectRevert_4Call { } impl Cheatcode for expectRevert_5Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, @@ -367,7 +367,7 @@ impl Cheatcode for expectRevert_5Call { } impl Cheatcode for expectPartialRevert_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert( ccx.state, @@ -381,7 +381,7 @@ impl Cheatcode for expectPartialRevert_0Call { } impl Cheatcode for expectPartialRevert_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData, reverter } = self; expect_revert( ccx.state, @@ -395,13 +395,13 @@ impl Cheatcode for expectPartialRevert_1Call { } impl Cheatcode for _expectCheatcodeRevert_0Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { expect_revert(ccx.state, None, ccx.ecx.journaled_state.depth(), true, false, None) } } impl Cheatcode for _expectCheatcodeRevert_1Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert( ccx.state, @@ -415,7 +415,7 @@ impl Cheatcode for _expectCheatcodeRevert_1Call { } impl Cheatcode for _expectCheatcodeRevert_2Call { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { revertData } = self; expect_revert( ccx.state, @@ -429,14 +429,14 @@ impl Cheatcode for _expectCheatcodeRevert_2Call { } impl Cheatcode for expectSafeMemoryCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth()) } } impl Cheatcode for stopExpectSafeMemoryCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self {} = self; ccx.state.allowed_mem_writes.remove(&ccx.ecx.journaled_state.depth()); Ok(Default::default()) @@ -444,7 +444,7 @@ impl Cheatcode for stopExpectSafeMemoryCall { } impl Cheatcode for expectSafeMemoryCallCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { min, max } = *self; expect_safe_memory(ccx.state, min, max, ccx.ecx.journaled_state.depth() + 1) } diff --git a/crates/cheatcodes/src/utils.rs b/crates/cheatcodes/src/utils.rs index eb4d2f525a4fb..23cc02f7ad9de 100644 --- a/crates/cheatcodes/src/utils.rs +++ b/crates/cheatcodes/src/utils.rs @@ -1,12 +1,12 @@ //! Implementations of [`Utilities`](spec::Group::Utilities) cheatcodes. -use crate::{Cheatcode, Cheatcodes, CheatsCtxt, Result, Vm::*}; +use crate::{Cheatcode, Cheatcodes, CheatcodesExecutor, CheatsCtxt, Result, Vm::*}; use alloy_dyn_abi::{DynSolType, DynSolValue}; use alloy_primitives::{map::HashMap, U256}; use alloy_sol_types::SolValue; use foundry_common::ens::namehash; -use foundry_evm_core::{backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER}; -use proptest::strategy::{Strategy, ValueTree}; +use foundry_evm_core::constants::DEFAULT_CREATE2_DEPLOYER; +use proptest::prelude::Strategy; use rand::{Rng, RngCore}; /// Contains locations of traces ignored via cheatcodes. @@ -134,10 +134,10 @@ impl Cheatcode for randomBytesCall { } impl Cheatcode for pauseTracingCall { - fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>( + fn apply_full( &self, - ccx: &mut crate::CheatsCtxt<DB>, - executor: &mut E, + ccx: &mut crate::CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else { // No tracer -> nothing to pause @@ -157,10 +157,10 @@ impl Cheatcode for pauseTracingCall { } impl Cheatcode for resumeTracingCall { - fn apply_full<DB: DatabaseExt, E: crate::CheatcodesExecutor>( + fn apply_full( &self, - ccx: &mut crate::CheatsCtxt<DB>, - executor: &mut E, + ccx: &mut crate::CheatsCtxt, + executor: &mut dyn CheatcodesExecutor, ) -> Result { let Some(tracer) = executor.tracing_inspector().and_then(|t| t.as_ref()) else { // No tracer -> nothing to unpause @@ -180,7 +180,7 @@ impl Cheatcode for resumeTracingCall { } impl Cheatcode for setArbitraryStorageCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { target } = self; ccx.state.arbitrary_storage().mark_arbitrary(target); @@ -189,7 +189,7 @@ impl Cheatcode for setArbitraryStorageCall { } impl Cheatcode for copyStorageCall { - fn apply_stateful<DB: DatabaseExt>(&self, ccx: &mut CheatsCtxt<DB>) -> Result { + fn apply_stateful(&self, ccx: &mut CheatsCtxt) -> Result { let Self { from, to } = self; ensure!( diff --git a/crates/chisel/src/executor.rs b/crates/chisel/src/executor.rs index 3d5fce2954547..5e40862f8277b 100644 --- a/crates/chisel/src/executor.rs +++ b/crates/chisel/src/executor.rs @@ -308,6 +308,7 @@ impl SessionSource { self.config.evm_opts.clone(), None, None, + None, Some(self.solc.version.clone()), ) .into(), diff --git a/crates/common/fmt/src/ui.rs b/crates/common/fmt/src/ui.rs index 64c5207690d7a..a82853145799b 100644 --- a/crates/common/fmt/src/ui.rs +++ b/crates/common/fmt/src/ui.rs @@ -622,6 +622,7 @@ mixHash {} nonce {} number {} parentHash {} +parentBeaconRoot {} transactionsRoot {} receiptsRoot {} sha3Uncles {} @@ -642,6 +643,7 @@ totalDifficulty {}", block.header.nonce.pretty(), block.header.number.pretty(), block.header.parent_hash.pretty(), + block.header.parent_beacon_block_root.pretty(), block.header.transactions_root.pretty(), block.header.receipts_root.pretty(), block.header.uncles_hash.pretty(), diff --git a/crates/common/src/fs.rs b/crates/common/src/fs.rs index 45c21eba69e2e..71a62d13a7ae7 100644 --- a/crates/common/src/fs.rs +++ b/crates/common/src/fs.rs @@ -56,6 +56,15 @@ pub fn write_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> { writer.flush().map_err(|e| FsPathError::write(e, path)) } +/// Writes the object as a pretty JSON object. +pub fn write_pretty_json_file<T: Serialize>(path: &Path, obj: &T) -> Result<()> { + let file = create_file(path)?; + let mut writer = BufWriter::new(file); + serde_json::to_writer_pretty(&mut writer, obj) + .map_err(|source| FsPathError::WriteJson { source, path: path.into() })?; + writer.flush().map_err(|e| FsPathError::write(e, path)) +} + /// Wrapper for `std::fs::write` pub fn write(path: impl AsRef<Path>, contents: impl AsRef<[u8]>) -> Result<()> { let path = path.as_ref(); diff --git a/crates/common/src/transactions.rs b/crates/common/src/transactions.rs index 9df8f896f99d0..7335714e54806 100644 --- a/crates/common/src/transactions.rs +++ b/crates/common/src/transactions.rs @@ -2,7 +2,10 @@ use alloy_consensus::{Transaction, TxEnvelope}; use alloy_primitives::{Address, TxKind, U256}; -use alloy_provider::{network::AnyNetwork, Provider}; +use alloy_provider::{ + network::{AnyNetwork, TransactionBuilder}, + Provider, +}; use alloy_rpc_types::{AnyTransactionReceipt, BlockId, TransactionRequest}; use alloy_serde::WithOtherFields; use alloy_transport::Transport; @@ -212,8 +215,8 @@ impl TransactionMaybeSigned { pub fn gas(&self) -> Option<u128> { match self { - Self::Signed { tx, .. } => Some(tx.gas_limit()), - Self::Unsigned(tx) => tx.gas, + Self::Signed { tx, .. } => Some(tx.gas_limit() as u128), + Self::Unsigned(tx) => tx.gas_limit().map(|g| g as u128), } } diff --git a/crates/config/src/lib.rs b/crates/config/src/lib.rs index 18352aab99729..017341c3d828f 100644 --- a/crates/config/src/lib.rs +++ b/crates/config/src/lib.rs @@ -175,6 +175,8 @@ pub struct Config { pub cache: bool, /// where the cache is stored if enabled pub cache_path: PathBuf, + /// where the gas snapshots are stored + pub snapshots: PathBuf, /// where the broadcast logs are stored pub broadcast: PathBuf, /// additional solc allow paths for `--allow-paths` @@ -718,6 +720,7 @@ impl Config { self.out = p(&root, &self.out); self.broadcast = p(&root, &self.broadcast); self.cache_path = p(&root, &self.cache_path); + self.snapshots = p(&root, &self.snapshots); if let Some(build_info_path) = self.build_info_path { self.build_info_path = Some(p(&root, &build_info_path)); @@ -885,6 +888,12 @@ impl Config { remove_test_dir(&self.fuzz.failure_persist_dir); remove_test_dir(&self.invariant.failure_persist_dir); + // Remove snapshot directory. + let snapshot_dir = project.root().join(&self.snapshots); + if snapshot_dir.exists() { + let _ = fs::remove_dir_all(&snapshot_dir); + } + Ok(()) } @@ -2086,6 +2095,7 @@ impl Default for Config { cache: true, cache_path: "cache".into(), broadcast: "broadcast".into(), + snapshots: "snapshots".into(), allow_paths: vec![], include_paths: vec![], force: false, diff --git a/crates/evm/core/src/backend/cow.rs b/crates/evm/core/src/backend/cow.rs index 8156d75fbddfd..1589c8ee8ec9f 100644 --- a/crates/evm/core/src/backend/cow.rs +++ b/crates/evm/core/src/backend/cow.rs @@ -62,20 +62,16 @@ impl<'a> CowBackend<'a> { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect<'b, I: InspectorExt<&'b mut dyn DatabaseExt>>( - &'b mut self, + pub fn inspect( + &mut self, env: &mut EnvWithHandlerCfg, - inspector: I, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<ResultAndState> { // this is a new call to inspect with a new env, so even if we've cloned the backend // already, we reset the initialized state self.is_initialized = false; self.spec_id = env.handler_cfg.spec_id; - let mut evm = crate::utils::new_evm_with_inspector( - self as &mut dyn DatabaseExt, - env.clone(), - inspector, - ); + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); let res = evm.transact().wrap_err("backend: failed while inspecting")?; @@ -190,7 +186,7 @@ impl<'a> DatabaseExt for CowBackend<'a> { transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt<Backend>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { self.backend_mut(env).transact(id, transaction, env, journaled_state, inspector) } @@ -200,7 +196,7 @@ impl<'a> DatabaseExt for CowBackend<'a> { transaction: TransactionRequest, env: &Env, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt<Backend>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { self.backend_mut(env).transact_from_tx(transaction, env, journaled_state, inspector) } diff --git a/crates/evm/core/src/backend/mod.rs b/crates/evm/core/src/backend/mod.rs index 49830bc892d03..9d26ce2873590 100644 --- a/crates/evm/core/src/backend/mod.rs +++ b/crates/evm/core/src/backend/mod.rs @@ -200,7 +200,7 @@ pub trait DatabaseExt: Database<Error = DatabaseError> + DatabaseCommit { transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt<Backend>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()>; /// Executes a given TransactionRequest, commits the new state to the DB @@ -209,7 +209,7 @@ pub trait DatabaseExt: Database<Error = DatabaseError> + DatabaseCommit { transaction: TransactionRequest, env: &Env, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt<Backend>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()>; /// Returns the `ForkId` that's currently used in the database, if fork mode is on @@ -751,17 +751,13 @@ impl Backend { /// Note: in case there are any cheatcodes executed that modify the environment, this will /// update the given `env` with the new values. #[instrument(name = "inspect", level = "debug", skip_all)] - pub fn inspect<'a, I: InspectorExt<&'a mut dyn DatabaseExt>>( - &'a mut self, + pub fn inspect( + &mut self, env: &mut EnvWithHandlerCfg, - inspector: I, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<ResultAndState> { self.initialize(env); - let mut evm = crate::utils::new_evm_with_inspector( - self as &mut dyn DatabaseExt, - env.clone(), - inspector, - ); + let mut evm = crate::utils::new_evm_with_inspector(self, env.clone(), inspector); let res = evm.transact().wrap_err("backend: failed while inspecting")?; @@ -1229,7 +1225,7 @@ impl DatabaseExt for Backend { transaction: B256, env: &mut Env, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt<Self>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { trace!(?maybe_id, ?transaction, "execute transaction"); let persistent_accounts = self.inner.persistent_accounts.clone(); @@ -1270,7 +1266,7 @@ impl DatabaseExt for Backend { tx: TransactionRequest, env: &Env, journaled_state: &mut JournaledState, - inspector: &mut dyn InspectorExt<Self>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { trace!(?tx, "execute signed transaction"); @@ -1279,7 +1275,7 @@ impl DatabaseExt for Backend { env.tx.caller = tx.from.ok_or_else(|| eyre::eyre!("transact_from_tx: No `from` field found"))?; env.tx.gas_limit = - tx.gas.ok_or_else(|| eyre::eyre!("transact_from_tx: No `gas` field found"))? as u64; + tx.gas.ok_or_else(|| eyre::eyre!("transact_from_tx: No `gas` field found"))?; env.tx.gas_price = U256::from(tx.gas_price.or(tx.max_fee_per_gas).unwrap_or_default()); env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(U256::from); env.tx.nonce = tx.nonce; @@ -1294,9 +1290,9 @@ impl DatabaseExt for Backend { self.commit(journaled_state.state.clone()); let res = { - let db = self.clone(); + let mut db = self.clone(); let env = self.env_with_handler_cfg(env); - let mut evm = new_evm_with_inspector(db, env, inspector); + let mut evm = new_evm_with_inspector(&mut db, env, inspector); evm.context.evm.journaled_state.depth = journaled_state.depth + 1; evm.transact()? }; @@ -1921,7 +1917,7 @@ fn commit_transaction( fork: &mut Fork, fork_id: &ForkId, persistent_accounts: &HashSet<Address>, - inspector: &mut dyn InspectorExt<Backend>, + inspector: &mut dyn InspectorExt, ) -> eyre::Result<()> { configure_tx_env(&mut env.env, tx); @@ -1930,9 +1926,9 @@ fn commit_transaction( let fork = fork.clone(); let journaled_state = journaled_state.clone(); let depth = journaled_state.depth; - let db = Backend::new_with_fork(fork_id, fork, journaled_state); + let mut db = Backend::new_with_fork(fork_id, fork, journaled_state); - let mut evm = crate::utils::new_evm_with_inspector(db, env, inspector); + let mut evm = crate::utils::new_evm_with_inspector(&mut db as _, env, inspector); // Adjust inner EVM depth to ensure that inspectors receive accurate data. evm.context.evm.inner.journaled_state.depth = depth + 1; evm.transact().wrap_err("backend: failed committing transaction")? diff --git a/crates/evm/core/src/lib.rs b/crates/evm/core/src/lib.rs index 0a8623a33cd0c..1a2ac4c4a0f49 100644 --- a/crates/evm/core/src/lib.rs +++ b/crates/evm/core/src/lib.rs @@ -6,7 +6,8 @@ #![cfg_attr(docsrs, feature(doc_cfg, doc_auto_cfg))] use auto_impl::auto_impl; -use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, Database, EvmContext, Inspector}; +use backend::DatabaseExt; +use revm::{inspectors::NoOpInspector, interpreter::CreateInputs, EvmContext, Inspector}; use revm_inspectors::access_list::AccessListInspector; #[macro_use] @@ -33,14 +34,14 @@ pub mod utils; /// An extension trait that allows us to add additional hooks to Inspector for later use in /// handlers. #[auto_impl(&mut, Box)] -pub trait InspectorExt<DB: Database>: Inspector<DB> { +pub trait InspectorExt: for<'a> Inspector<&'a mut dyn DatabaseExt> { /// Determines whether the `DEFAULT_CREATE2_DEPLOYER` should be used for a CREATE2 frame. /// /// If this function returns true, we'll replace CREATE2 frame with a CALL frame to CREATE2 /// factory. fn should_use_create2_factory( &mut self, - _context: &mut EvmContext<DB>, + _context: &mut EvmContext<&mut dyn DatabaseExt>, _inputs: &mut CreateInputs, ) -> bool { false @@ -55,5 +56,6 @@ pub trait InspectorExt<DB: Database>: Inspector<DB> { } } -impl<DB: Database> InspectorExt<DB> for NoOpInspector {} -impl<DB: Database> InspectorExt<DB> for AccessListInspector {} +impl InspectorExt for NoOpInspector {} + +impl InspectorExt for AccessListInspector {} diff --git a/crates/evm/core/src/utils.rs b/crates/evm/core/src/utils.rs index 11f8bbb318827..d45c4b84908a8 100644 --- a/crates/evm/core/src/utils.rs +++ b/crates/evm/core/src/utils.rs @@ -1,5 +1,8 @@ pub use crate::ic::*; -use crate::{constants::DEFAULT_CREATE2_DEPLOYER, precompiles::ALPHANET_P256, InspectorExt}; +use crate::{ + backend::DatabaseExt, constants::DEFAULT_CREATE2_DEPLOYER, precompiles::ALPHANET_P256, + InspectorExt, +}; use alloy_json_abi::{Function, JsonAbi}; use alloy_primitives::{Address, Selector, TxKind, U256}; use alloy_provider::{ @@ -8,8 +11,8 @@ use alloy_provider::{ }; use alloy_rpc_types::Transaction; use foundry_config::NamedChain; +use foundry_fork_db::DatabaseError; use revm::{ - db::WrapDatabaseRef, handler::register::EvmHandler, interpreter::{ return_ok, CallInputs, CallOutcome, CallScheme, CallValue, CreateInputs, CreateOutcome, @@ -84,7 +87,7 @@ pub fn get_function<'a>( /// Configures the env for the transaction pub fn configure_tx_env(env: &mut revm::primitives::Env, tx: &Transaction) { env.tx.caller = tx.from; - env.tx.gas_limit = tx.gas as u64; + env.tx.gas_limit = tx.gas; env.tx.gas_price = U256::from(tx.gas_price.unwrap_or_default()); env.tx.gas_priority_fee = tx.max_priority_fee_per_gas.map(U256::from); env.tx.nonce = Some(tx.nonce); @@ -123,15 +126,15 @@ fn get_create2_factory_call_inputs(salt: U256, inputs: CreateInputs) -> CallInpu /// hook by inserting decoded address directly into interpreter. /// /// Should be installed after [revm::inspector_handle_register] and before any other registers. -pub fn create2_handler_register<DB: revm::Database, I: InspectorExt<DB>>( - handler: &mut EvmHandler<'_, I, DB>, +pub fn create2_handler_register<I: InspectorExt>( + handler: &mut EvmHandler<'_, I, &mut dyn DatabaseExt>, ) { let create2_overrides = Rc::<RefCell<Vec<_>>>::new(RefCell::new(Vec::new())); let create2_overrides_inner = create2_overrides.clone(); let old_handle = handler.execution.create.clone(); handler.execution.create = - Arc::new(move |ctx, mut inputs| -> Result<FrameOrResult, EVMError<DB::Error>> { + Arc::new(move |ctx, mut inputs| -> Result<FrameOrResult, EVMError<DatabaseError>> { let CreateScheme::Create2 { salt } = inputs.scheme else { return old_handle(ctx, inputs); }; @@ -219,9 +222,7 @@ pub fn create2_handler_register<DB: revm::Database, I: InspectorExt<DB>>( } /// Adds Alphanet P256 precompile to the list of loaded precompiles. -pub fn alphanet_handler_register<DB: revm::Database, I: InspectorExt<DB>>( - handler: &mut EvmHandler<'_, I, DB>, -) { +pub fn alphanet_handler_register<EXT, DB: revm::Database>(handler: &mut EvmHandler<'_, EXT, DB>) { let prev = handler.pre_execution.load_precompiles.clone(); handler.pre_execution.load_precompiles = Arc::new(move || { let mut loaded_precompiles = prev(); @@ -233,15 +234,11 @@ pub fn alphanet_handler_register<DB: revm::Database, I: InspectorExt<DB>>( } /// Creates a new EVM with the given inspector. -pub fn new_evm_with_inspector<'a, DB, I>( - db: DB, +pub fn new_evm_with_inspector<'evm, 'i, 'db>( + db: &'db mut dyn DatabaseExt, env: revm::primitives::EnvWithHandlerCfg, - inspector: I, -) -> revm::Evm<'a, I, DB> -where - DB: revm::Database, - I: InspectorExt<DB>, -{ + inspector: &'i mut dyn InspectorExt, +) -> revm::Evm<'evm, &'i mut dyn InspectorExt, &'db mut dyn DatabaseExt> { let revm::primitives::EnvWithHandlerCfg { env, handler_cfg } = env; // NOTE: We could use `revm::Evm::builder()` here, but on the current patch it has some @@ -269,27 +266,10 @@ where revm::Evm::new(context, handler) } -/// Creates a new EVM with the given inspector and wraps the database in a `WrapDatabaseRef`. -pub fn new_evm_with_inspector_ref<'a, DB, I>( - db: DB, - env: revm::primitives::EnvWithHandlerCfg, - inspector: I, -) -> revm::Evm<'a, I, WrapDatabaseRef<DB>> -where - DB: revm::DatabaseRef, - I: InspectorExt<WrapDatabaseRef<DB>>, -{ - new_evm_with_inspector(WrapDatabaseRef(db), env, inspector) -} - -pub fn new_evm_with_existing_context<'a, DB, I>( - inner: revm::InnerEvmContext<DB>, - inspector: I, -) -> revm::Evm<'a, I, DB> -where - DB: revm::Database, - I: InspectorExt<DB>, -{ +pub fn new_evm_with_existing_context<'a>( + inner: revm::InnerEvmContext<&'a mut dyn DatabaseExt>, + inspector: &'a mut dyn InspectorExt, +) -> revm::Evm<'a, &'a mut dyn InspectorExt, &'a mut dyn DatabaseExt> { let handler_cfg = HandlerCfg::new(inner.spec_id()); let mut handler = revm::Handler::new(handler_cfg); @@ -303,24 +283,3 @@ where revm::Context::new(revm::EvmContext { inner, precompiles: Default::default() }, inspector); revm::Evm::new(context, handler) } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn build_evm() { - let mut db = revm::db::EmptyDB::default(); - - let env = Box::<revm::primitives::Env>::default(); - let spec = SpecId::LATEST; - let handler_cfg = revm::primitives::HandlerCfg::new(spec); - let cfg = revm::primitives::EnvWithHandlerCfg::new(env, handler_cfg); - - let mut inspector = revm::inspectors::NoOpInspector; - - let mut evm = new_evm_with_inspector(&mut db, cfg, &mut inspector); - let result = evm.transact().unwrap(); - assert!(result.result.is_success()); - } -} diff --git a/crates/evm/evm/src/executors/fuzz/mod.rs b/crates/evm/evm/src/executors/fuzz/mod.rs index cb57f205326ab..982e44ea9d6a2 100644 --- a/crates/evm/evm/src/executors/fuzz/mod.rs +++ b/crates/evm/evm/src/executors/fuzz/mod.rs @@ -17,7 +17,7 @@ use foundry_evm_fuzz::{ use foundry_evm_traces::SparsedTraceArena; use indicatif::ProgressBar; use proptest::test_runner::{TestCaseError, TestError, TestRunner}; -use std::cell::RefCell; +use std::{cell::RefCell, collections::BTreeMap}; mod types; pub use types::{CaseOutcome, CounterExampleOutcome, FuzzOutcome}; @@ -39,6 +39,8 @@ pub struct FuzzTestData { pub coverage: Option<HitMaps>, // Stores logs for all fuzz cases pub logs: Vec<Log>, + // Stores gas snapshots for all fuzz cases + pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>, // Deprecated cheatcodes mapped to their replacements. pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, } @@ -108,9 +110,11 @@ impl FuzzedExecutor { FuzzOutcome::Case(case) => { let mut data = execution_data.borrow_mut(); data.gas_by_case.push((case.case.gas, case.case.stipend)); + if data.first_case.is_none() { data.first_case.replace(case.case); } + if let Some(call_traces) = case.traces { if data.traces.len() == max_traces_to_collect { data.traces.pop(); @@ -118,14 +122,17 @@ impl FuzzedExecutor { data.traces.push(call_traces); data.breakpoints.replace(case.breakpoints); } + if show_logs { data.logs.extend(case.logs); } + // Collect and merge coverage if `forge snapshot` context. match &mut data.coverage { Some(prev) => prev.merge(case.coverage.unwrap()), opt => *opt = case.coverage, } + data.deprecated_cheatcodes = case.deprecated_cheatcodes; Ok(()) diff --git a/crates/evm/evm/src/inspectors/logs.rs b/crates/evm/evm/src/inspectors/logs.rs index 03b677fcdf844..877101a8064f0 100644 --- a/crates/evm/evm/src/inspectors/logs.rs +++ b/crates/evm/evm/src/inspectors/logs.rs @@ -60,7 +60,7 @@ impl<DB: Database> Inspector<DB> for LogCollector { gas: Gas::new(inputs.gas_limit), }, memory_offset: inputs.return_memory_offset.clone(), - }) + }); } } @@ -68,7 +68,7 @@ impl<DB: Database> Inspector<DB> for LogCollector { } } -impl<DB: Database> InspectorExt<DB> for LogCollector { +impl InspectorExt for LogCollector { fn console_log(&mut self, input: String) { self.logs.push(Log::new_unchecked( HARDHAT_CONSOLE_ADDRESS, diff --git a/crates/evm/evm/src/inspectors/stack.rs b/crates/evm/evm/src/inspectors/stack.rs index fc270564133ad..c8df2c693102c 100644 --- a/crates/evm/evm/src/inspectors/stack.rs +++ b/crates/evm/evm/src/inspectors/stack.rs @@ -304,11 +304,8 @@ pub struct InspectorStackRefMut<'a> { } impl CheatcodesExecutor for InspectorStackInner { - fn get_inspector<'a, DB: DatabaseExt>( - &'a mut self, - cheats: &'a mut Cheatcodes, - ) -> impl InspectorExt<DB> + 'a { - InspectorStackRefMut { cheatcodes: Some(cheats), inner: self } + fn get_inspector<'a>(&'a mut self, cheats: &'a mut Cheatcodes) -> Box<dyn InspectorExt + 'a> { + Box::new(InspectorStackRefMut { cheatcodes: Some(cheats), inner: self }) } fn tracing_inspector(&mut self) -> Option<&mut Option<TracingInspector>> { @@ -479,15 +476,15 @@ impl<'a> InspectorStackRefMut<'a> { /// Should be called on the top-level call of inner context (depth == 0 && /// self.in_inner_context) Decreases sender nonce for CALLs to keep backwards compatibility /// Updates tx.origin to the value before entering inner context - fn adjust_evm_data_for_inner_context<DB: DatabaseExt>(&mut self, ecx: &mut EvmContext<DB>) { + fn adjust_evm_data_for_inner_context(&mut self, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { let inner_context_data = self.inner_context_data.as_ref().expect("should be called in inner context"); ecx.env.tx.caller = inner_context_data.original_origin; } - fn do_call_end<DB: DatabaseExt>( + fn do_call_end( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, inputs: &CallInputs, outcome: CallOutcome, ) -> CallOutcome { @@ -512,9 +509,9 @@ impl<'a> InspectorStackRefMut<'a> { outcome } - fn transact_inner<DB: DatabaseExt>( + fn transact_inner( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, transact_to: TransactTo, caller: Address, input: Bytes, @@ -547,11 +544,7 @@ impl<'a> InspectorStackRefMut<'a> { let env = EnvWithHandlerCfg::new_with_spec_id(ecx.env.clone(), ecx.spec_id()); let res = { - let mut evm = crate::utils::new_evm_with_inspector( - &mut ecx.db as &mut dyn DatabaseExt, - env, - &mut *self, - ); + let mut evm = crate::utils::new_evm_with_inspector(&mut ecx.db, env, self); let res = evm.transact(); // need to reset the env in case it was modified via cheatcodes during execution @@ -630,8 +623,12 @@ impl<'a> InspectorStackRefMut<'a> { } } -impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { - fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { +impl<'a> Inspector<&mut dyn DatabaseExt> for InspectorStackRefMut<'a> { + fn initialize_interp( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { call_inspectors_adjust_depth!( [&mut self.coverage, &mut self.tracer, &mut self.cheatcodes, &mut self.printer], |inspector| inspector.initialize_interp(interpreter, ecx), @@ -640,7 +637,7 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { ); } - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { call_inspectors_adjust_depth!( [ &mut self.fuzzer, @@ -655,7 +652,11 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { ); } - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn step_end( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { call_inspectors_adjust_depth!( [&mut self.tracer, &mut self.cheatcodes, &mut self.chisel_state, &mut self.printer], |inspector| inspector.step_end(interpreter, ecx), @@ -664,7 +665,12 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { ); } - fn log(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>, log: &Log) { + fn log( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + log: &Log, + ) { call_inspectors_adjust_depth!( [&mut self.tracer, &mut self.log_collector, &mut self.cheatcodes, &mut self.printer], |inspector| inspector.log(interpreter, ecx, log), @@ -673,7 +679,11 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { ); } - fn call(&mut self, ecx: &mut EvmContext<DB>, call: &mut CallInputs) -> Option<CallOutcome> { + fn call( + &mut self, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + call: &mut CallInputs, + ) -> Option<CallOutcome> { if self.in_inner_context && ecx.journaled_state.depth == 0 { self.adjust_evm_data_for_inner_context(ecx); return None; @@ -739,7 +749,7 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { fn call_end( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, inputs: &CallInputs, outcome: CallOutcome, ) -> CallOutcome { @@ -764,7 +774,7 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { fn create( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, create: &mut CreateInputs, ) -> Option<CreateOutcome> { if self.in_inner_context && ecx.journaled_state.depth == 0 { @@ -801,7 +811,7 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { fn create_end( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, call: &CreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { @@ -835,7 +845,7 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { fn eofcreate( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, create: &mut EOFCreateInputs, ) -> Option<CreateOutcome> { if self.in_inner_context && ecx.journaled_state.depth == 0 { @@ -877,7 +887,7 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { fn eofcreate_end( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, call: &EOFCreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { @@ -911,15 +921,15 @@ impl<'a, DB: DatabaseExt> Inspector<DB> for InspectorStackRefMut<'a> { fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { call_inspectors!([&mut self.tracer, &mut self.printer], |inspector| { - Inspector::<DB>::selfdestruct(inspector, contract, target, value) + Inspector::<&mut dyn DatabaseExt>::selfdestruct(inspector, contract, target, value) }); } } -impl<'a, DB: DatabaseExt> InspectorExt<DB> for InspectorStackRefMut<'a> { +impl InspectorExt for InspectorStackRefMut<'_> { fn should_use_create2_factory( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, inputs: &mut CreateInputs, ) -> bool { call_inspectors_adjust_depth!( @@ -934,7 +944,7 @@ impl<'a, DB: DatabaseExt> InspectorExt<DB> for InspectorStackRefMut<'a> { } fn console_log(&mut self, input: String) { - call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::<DB>::console_log( + call_inspectors!([&mut self.log_collector], |inspector| InspectorExt::console_log( inspector, input )); } @@ -944,20 +954,24 @@ impl<'a, DB: DatabaseExt> InspectorExt<DB> for InspectorStackRefMut<'a> { } } -impl<DB: DatabaseExt> Inspector<DB> for InspectorStack { +impl Inspector<&mut dyn DatabaseExt> for InspectorStack { #[inline] - fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn step(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<&mut dyn DatabaseExt>) { self.as_mut().step(interpreter, ecx) } #[inline] - fn step_end(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn step_end( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { self.as_mut().step_end(interpreter, ecx) } fn call( &mut self, - context: &mut EvmContext<DB>, + context: &mut EvmContext<&mut dyn DatabaseExt>, inputs: &mut CallInputs, ) -> Option<CallOutcome> { self.as_mut().call(context, inputs) @@ -965,7 +979,7 @@ impl<DB: DatabaseExt> Inspector<DB> for InspectorStack { fn call_end( &mut self, - context: &mut EvmContext<DB>, + context: &mut EvmContext<&mut dyn DatabaseExt>, inputs: &CallInputs, outcome: CallOutcome, ) -> CallOutcome { @@ -974,7 +988,7 @@ impl<DB: DatabaseExt> Inspector<DB> for InspectorStack { fn create( &mut self, - context: &mut EvmContext<DB>, + context: &mut EvmContext<&mut dyn DatabaseExt>, create: &mut CreateInputs, ) -> Option<CreateOutcome> { self.as_mut().create(context, create) @@ -982,7 +996,7 @@ impl<DB: DatabaseExt> Inspector<DB> for InspectorStack { fn create_end( &mut self, - context: &mut EvmContext<DB>, + context: &mut EvmContext<&mut dyn DatabaseExt>, call: &CreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { @@ -991,7 +1005,7 @@ impl<DB: DatabaseExt> Inspector<DB> for InspectorStack { fn eofcreate( &mut self, - context: &mut EvmContext<DB>, + context: &mut EvmContext<&mut dyn DatabaseExt>, create: &mut EOFCreateInputs, ) -> Option<CreateOutcome> { self.as_mut().eofcreate(context, create) @@ -999,30 +1013,39 @@ impl<DB: DatabaseExt> Inspector<DB> for InspectorStack { fn eofcreate_end( &mut self, - context: &mut EvmContext<DB>, + context: &mut EvmContext<&mut dyn DatabaseExt>, call: &EOFCreateInputs, outcome: CreateOutcome, ) -> CreateOutcome { self.as_mut().eofcreate_end(context, call, outcome) } - fn initialize_interp(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>) { + fn initialize_interp( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + ) { self.as_mut().initialize_interp(interpreter, ecx) } - fn log(&mut self, interpreter: &mut Interpreter, ecx: &mut EvmContext<DB>, log: &Log) { + fn log( + &mut self, + interpreter: &mut Interpreter, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, + log: &Log, + ) { self.as_mut().log(interpreter, ecx, log) } fn selfdestruct(&mut self, contract: Address, target: Address, value: U256) { - Inspector::<DB>::selfdestruct(&mut self.as_mut(), contract, target, value) + Inspector::<&mut dyn DatabaseExt>::selfdestruct(&mut self.as_mut(), contract, target, value) } } -impl<DB: DatabaseExt> InspectorExt<DB> for InspectorStack { +impl InspectorExt for InspectorStack { fn should_use_create2_factory( &mut self, - ecx: &mut EvmContext<DB>, + ecx: &mut EvmContext<&mut dyn DatabaseExt>, inputs: &mut CreateInputs, ) -> bool { self.as_mut().should_use_create2_factory(ecx, inputs) diff --git a/crates/forge/bin/cmd/test/mod.rs b/crates/forge/bin/cmd/test/mod.rs index 6bf1ffd8183a4..6d53a4756bd77 100644 --- a/crates/forge/bin/cmd/test/mod.rs +++ b/crates/forge/bin/cmd/test/mod.rs @@ -310,6 +310,17 @@ impl TestArgs { let toml = config.get_config_path(); let profiles = get_available_profiles(toml)?; + // Remove the snapshots directory if it exists. + // This is to ensure that we don't have any stale snapshots. + // If `FORGE_SNAPSHOT_CHECK` is set, we don't remove the snapshots directory as it is + // required for comparison. + if std::env::var("FORGE_SNAPSHOT_CHECK").is_err() { + let snapshot_dir = project_root.join(&config.snapshots); + if snapshot_dir.exists() { + let _ = fs::remove_dir_all(project_root.join(&config.snapshots)); + } + } + let test_options: TestOptions = TestOptionsBuilder::default() .fuzz(config.fuzz.clone()) .invariant(config.invariant.clone()) @@ -546,6 +557,8 @@ impl TestArgs { .gas_report .then(|| GasReport::new(config.gas_reports.clone(), config.gas_reports_ignore.clone())); + let mut gas_snapshots = BTreeMap::<String, BTreeMap<String, String>>::new(); + let mut outcome = TestOutcome::empty(self.allow_failure); let mut any_test_failed = false; @@ -655,6 +668,83 @@ impl TestArgs { } } } + + // Collect and merge gas snapshots. + for (group, new_snapshots) in result.gas_snapshots.iter() { + gas_snapshots.entry(group.clone()).or_default().extend(new_snapshots.clone()); + } + } + + // Write gas snapshots to disk if any were collected. + if !gas_snapshots.is_empty() { + // Check for differences in gas snapshots if `FORGE_SNAPSHOT_CHECK` is set. + // Exiting early with code 1 if differences are found. + if std::env::var("FORGE_SNAPSHOT_CHECK").is_ok() { + let differences_found = gas_snapshots.clone().into_iter().fold( + false, + |mut found, (group, snapshots)| { + // If the snapshot file doesn't exist, we can't compare so we skip. + if !&config.snapshots.join(format!("{group}.json")).exists() { + return false; + } + + let previous_snapshots: BTreeMap<String, String> = + fs::read_json_file(&config.snapshots.join(format!("{group}.json"))) + .expect("Failed to read snapshots from disk"); + + let diff: BTreeMap<_, _> = snapshots + .iter() + .filter_map(|(k, v)| { + previous_snapshots.get(k).and_then(|previous_snapshot| { + if previous_snapshot != v { + Some(( + k.clone(), + (previous_snapshot.clone(), v.clone()), + )) + } else { + None + } + }) + }) + .collect(); + + if !diff.is_empty() { + println!( + "{}", + format!("\n[{group}] Failed to match snapshots:").red().bold() + ); + + for (key, (previous_snapshot, snapshot)) in &diff { + println!( + "{}", + format!("- [{key}] {previous_snapshot} → {snapshot}").red() + ); + } + + found = true; + } + + found + }, + ); + + if differences_found { + println!(); + eyre::bail!("Snapshots differ from previous run"); + } + } + + // Create `snapshots` directory if it doesn't exist. + fs::create_dir_all(&config.snapshots)?; + + // Write gas snapshots to disk per group. + gas_snapshots.clone().into_iter().for_each(|(group, snapshots)| { + fs::write_pretty_json_file( + &config.snapshots.join(format!("{group}.json")), + &snapshots, + ) + .expect("Failed to write gas snapshots to disk"); + }); } // Print suite summary. diff --git a/crates/forge/src/multi_runner.rs b/crates/forge/src/multi_runner.rs index 43aade0ff65c1..802ad3884cc65 100644 --- a/crates/forge/src/multi_runner.rs +++ b/crates/forge/src/multi_runner.rs @@ -243,6 +243,7 @@ impl MultiContractRunner { self.evm_opts.clone(), Some(self.known_contracts.clone()), None, + Some(artifact_id.name.clone()), Some(artifact_id.version.clone()), ); diff --git a/crates/forge/src/result.rs b/crates/forge/src/result.rs index 1ec829f6bc942..0e00bb5e2773e 100644 --- a/crates/forge/src/result.rs +++ b/crates/forge/src/result.rs @@ -412,6 +412,9 @@ pub struct TestResult { /// pc breakpoint char map pub breakpoints: Breakpoints, + /// Any captured gas snapshots along the test's execution which should be accumulated. + pub gas_snapshots: BTreeMap<String, BTreeMap<String, String>>, + /// Deprecated cheatcodes (mapped to their replacements, if any) used in current test. #[serde(skip)] pub deprecated_cheatcodes: HashMap<&'static str, Option<&'static str>>, @@ -531,6 +534,7 @@ impl TestResult { if let Some(cheatcodes) = raw_call_result.cheatcodes { self.breakpoints = cheatcodes.breakpoints; + self.gas_snapshots = cheatcodes.gas_snapshots; self.deprecated_cheatcodes = cheatcodes.deprecated; } diff --git a/crates/forge/tests/cli/config.rs b/crates/forge/tests/cli/config.rs index f8b1da523c74c..5c0d62a3299af 100644 --- a/crates/forge/tests/cli/config.rs +++ b/crates/forge/tests/cli/config.rs @@ -37,6 +37,7 @@ forgetest!(can_extract_config_values, |prj, cmd| { libs: vec!["lib-test".into()], cache: true, cache_path: "test-cache".into(), + snapshots: "snapshots".into(), broadcast: "broadcast".into(), force: true, evm_version: EvmVersion::Byzantium, diff --git a/crates/script/src/broadcast.rs b/crates/script/src/broadcast.rs index ac893b19f7aab..3faaa441e24c2 100644 --- a/crates/script/src/broadcast.rs +++ b/crates/script/src/broadcast.rs @@ -43,7 +43,7 @@ where tx.set_gas_limit( provider.estimate_gas(tx).await.wrap_err("Failed to estimate gas for tx")? * - estimate_multiplier as u128 / + estimate_multiplier / 100, ); Ok(()) diff --git a/crates/script/src/lib.rs b/crates/script/src/lib.rs index 8231a5d54b561..94c028bb9b471 100644 --- a/crates/script/src/lib.rs +++ b/crates/script/src/lib.rs @@ -612,6 +612,7 @@ impl ScriptConfig { self.evm_opts.clone(), Some(known_contracts), Some(script_wallets), + Some(target.name), Some(target.version), ) .into(), diff --git a/crates/script/src/transaction.rs b/crates/script/src/transaction.rs index bbf84ff0ff8ae..639c5f022ac18 100644 --- a/crates/script/src/transaction.rs +++ b/crates/script/src/transaction.rs @@ -121,7 +121,7 @@ impl TransactionWithMetadata { if !self.is_fixed_gas_limit { if let Some(unsigned) = self.transaction.as_unsigned_mut() { // We inflate the gas used by the user specified percentage - unsigned.gas = Some((result.gas_used * gas_estimate_multiplier / 100) as u128); + unsigned.gas = Some(result.gas_used * gas_estimate_multiplier / 100); } } diff --git a/crates/verify/src/bytecode.rs b/crates/verify/src/bytecode.rs index 034bb49a68461..661eb5c8dcb33 100644 --- a/crates/verify/src/bytecode.rs +++ b/crates/verify/src/bytecode.rs @@ -253,9 +253,9 @@ impl VerifyBytecodeArgs { if let Some(ref block) = genesis_block { configure_env_block(&mut env, block); - gen_tx.max_fee_per_gas = Some(block.header.base_fee_per_gas.unwrap_or_default()); + gen_tx.max_fee_per_gas = block.header.base_fee_per_gas.map(|g| g as u128); gen_tx.gas = block.header.gas_limit; - gen_tx.gas_price = Some(block.header.base_fee_per_gas.unwrap_or_default()); + gen_tx.gas_price = block.header.base_fee_per_gas.map(|g| g as u128); } configure_tx_env(&mut env, &gen_tx); diff --git a/crates/wallets/src/multi_wallet.rs b/crates/wallets/src/multi_wallet.rs index 28c3e31a168d5..c593e67e3d510 100644 --- a/crates/wallets/src/multi_wallet.rs +++ b/crates/wallets/src/multi_wallet.rs @@ -162,7 +162,7 @@ pub struct MultiWalletOpts { )] pub mnemonic_indexes: Option<Vec<u32>>, - /// Use the keystore in the given folder or file. + /// Use the keystore by its filename in the given folder. #[arg( long = "keystore", visible_alias = "keystores", @@ -173,7 +173,7 @@ pub struct MultiWalletOpts { #[builder(default = "None")] pub keystore_paths: Option<Vec<String>>, - /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename + /// Use a keystore from the default keystores folder (~/.foundry/keystores) by its filename. #[arg( long = "account", visible_alias = "accounts", diff --git a/testdata/cheats/Vm.sol b/testdata/cheats/Vm.sol index fc0b66c325c41..6bb6ad46c0a40 100644 --- a/testdata/cheats/Vm.sol +++ b/testdata/cheats/Vm.sol @@ -424,7 +424,11 @@ interface Vm { function skip(bool skipTest, string calldata reason) external; function sleep(uint256 duration) external; function snapshot() external returns (uint256 snapshotId); + function snapshotGasLastCall(string calldata name) external returns (uint256 gasUsed); + function snapshotGasLastCall(string calldata group, string calldata name) external returns (uint256 gasUsed); function snapshotState() external returns (uint256 snapshotId); + function snapshotValue(string calldata name, uint256 value) external; + function snapshotValue(string calldata group, string calldata name, uint256 value) external; function split(string calldata input, string calldata delimiter) external pure returns (string[] memory outputs); function startBroadcast() external; function startBroadcast(address signer) external; @@ -433,6 +437,8 @@ interface Vm { function startMappingRecording() external; function startPrank(address msgSender) external; function startPrank(address msgSender, address txOrigin) external; + function startSnapshotGas(string calldata name) external; + function startSnapshotGas(string calldata group, string calldata name) external; function startStateDiffRecording() external; function stopAndReturnDebugTraceRecording() external returns (DebugStep[] memory step); function stopAndReturnStateDiff() external returns (AccountAccess[] memory accountAccesses); @@ -440,6 +446,9 @@ interface Vm { function stopExpectSafeMemory() external; function stopMappingRecording() external; function stopPrank() external; + function stopSnapshotGas() external returns (uint256 gasUsed); + function stopSnapshotGas(string calldata name) external returns (uint256 gasUsed); + function stopSnapshotGas(string calldata group, string calldata name) external returns (uint256 gasUsed); function store(address target, bytes32 slot, bytes32 value) external; function toBase64URL(bytes calldata data) external pure returns (string memory); function toBase64URL(string calldata data) external pure returns (string memory); diff --git a/testdata/default/cheats/GasSnapshots.t.sol b/testdata/default/cheats/GasSnapshots.t.sol new file mode 100644 index 0000000000000..1e64a073d11fd --- /dev/null +++ b/testdata/default/cheats/GasSnapshots.t.sol @@ -0,0 +1,321 @@ +// SPDX-License-Identifier: MIT OR Apache-2.0 +pragma solidity 0.8.18; + +import "ds-test/test.sol"; +import "cheats/Vm.sol"; + +contract GasSnapshotTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + uint256 public slot0; + Flare public flare; + + function setUp() public { + flare = new Flare(); + } + + function testSnapshotGasSectionExternal() public { + vm.startSnapshotGas("testAssertGasExternal"); + flare.run(1); + uint256 gasUsed = vm.stopSnapshotGas(); + + assertGt(gasUsed, 0); + } + + function testSnapshotGasSectionInternal() public { + vm.startSnapshotGas("testAssertGasInternalA"); + slot0 = 1; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalB"); + slot0 = 2; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalC"); + slot0 = 0; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalD"); + slot0 = 1; + vm.stopSnapshotGas(); + + vm.startSnapshotGas("testAssertGasInternalE"); + slot0 = 2; + vm.stopSnapshotGas(); + } + + // Writes to `GasSnapshotTest` group with custom names. + function testSnapshotValueDefaultGroupA() public { + uint256 a = 123; + uint256 b = 456; + uint256 c = 789; + + vm.snapshotValue("a", a); + vm.snapshotValue("b", b); + vm.snapshotValue("c", c); + } + + // Writes to same `GasSnapshotTest` group with custom names. + function testSnapshotValueDefaultGroupB() public { + uint256 d = 123; + uint256 e = 456; + uint256 f = 789; + + vm.snapshotValue("d", d); + vm.snapshotValue("e", e); + vm.snapshotValue("f", f); + } + + // Writes to `CustomGroup` group with custom names. + // Asserts that the order of the values is alphabetical. + function testSnapshotValueCustomGroupA() public { + uint256 o = 123; + uint256 i = 456; + uint256 q = 789; + + vm.snapshotValue("CustomGroup", "q", q); + vm.snapshotValue("CustomGroup", "i", i); + vm.snapshotValue("CustomGroup", "o", o); + } + + // Writes to `CustomGroup` group with custom names. + // Asserts that the order of the values is alphabetical. + function testSnapshotValueCustomGroupB() public { + uint256 x = 123; + uint256 e = 456; + uint256 z = 789; + + vm.snapshotValue("CustomGroup", "z", z); + vm.snapshotValue("CustomGroup", "x", x); + vm.snapshotValue("CustomGroup", "e", e); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasDefault` name. + function testSnapshotGasSectionDefaultGroupStop() public { + vm.startSnapshotGas("testSnapshotGasSection"); + + flare.run(256); + + // vm.stopSnapshotGas() will use the last snapshot name. + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasCustom` name. + function testSnapshotGasSectionCustomGroupStop() public { + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSection"); + + flare.run(256); + + // vm.stopSnapshotGas() will use the last snapshot name, even with custom group. + uint256 gasUsed = vm.stopSnapshotGas(); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionName() public { + vm.startSnapshotGas("testSnapshotGasSectionName"); + + flare.run(256); + + uint256 gasUsed = vm.stopSnapshotGas("testSnapshotGasSectionName"); + assertGt(gasUsed, 0); + } + + // Writes to `CustomGroup` group with `testSnapshotGasSection` name. + function testSnapshotGasSectionGroupName() public { + vm.startSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + + flare.run(256); + + uint256 gasUsed = vm.stopSnapshotGas("CustomGroup", "testSnapshotGasSectionGroupName"); + assertGt(gasUsed, 0); + } + + // Writes to `GasSnapshotTest` group with `testSnapshotGas` name. + function testSnapshotGasLastCallName() public { + flare.run(1); + + uint256 gasUsed = vm.snapshotGasLastCall("testSnapshotGasLastCallName"); + assertGt(gasUsed, 0); + } + + // Writes to `CustomGroup` group with `testSnapshotGas` name. + function testSnapshotGasLastCallGroupName() public { + flare.run(1); + + uint256 gasUsed = vm.snapshotGasLastCall("CustomGroup", "testSnapshotGasLastCallGroupName"); + assertGt(gasUsed, 0); + } +} + +contract GasComparisonTest is DSTest { + Vm constant vm = Vm(HEVM_ADDRESS); + + uint256 public slot0; + uint256 public slot1; + + uint256 public cachedGas; + + function testGasComparisonEmpty() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonEmptyA"); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonEmptyB", b); + + assertEq(a, b); + } + + function testGasComparisonInternalCold() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonInternalColdA"); + slot0 = 1; + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + slot1 = 1; + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonInternalColdB", b); + + vm.assertApproxEqAbs(a, b, 6); + } + + function testGasComparisonInternalWarm() public { + // Warm up the cache. + slot0 = 1; + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonInternalWarmA"); + slot0 = 2; + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + slot0 = 3; + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonInternalWarmB", b); + + vm.assertApproxEqAbs(a, b, 6); + } + + function testGasComparisonExternal() public { + // Warm up the cache. + TargetB target = new TargetB(); + target.update(1); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonExternalA"); + target.update(2); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + target.update(3); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonExternalB", b); + + assertEq(a, b); + } + + function testGasComparisonCreate() public { + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonCreateA"); + new TargetC(); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + new TargetC(); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonCreateB", b); + + assertEq(a, b); + } + + function testGasComparisonNestedCalls() public { + // Warm up the cache. + TargetA target = new TargetA(); + target.update(1); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonNestedCallsA"); + target.update(2); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + target.update(3); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonNestedCallsB", b); + + assertEq(a, b); + } + + function testGasComparisonFlare() public { + // Warm up the cache. + Flare flare = new Flare(); + flare.run(1); + + // Start a cheatcode snapshot. + vm.startSnapshotGas("ComparisonGroup", "testGasComparisonFlareA"); + flare.run(256); + uint256 a = vm.stopSnapshotGas(); + + // Start a comparitive Solidity snapshot. + _snapStart(); + flare.run(256); + uint256 b = _snapEnd(); + vm.snapshotValue("ComparisonGroup", "testGasComparisonFlareB", b); + + assertEq(a, b); + } + + // Internal function to start a Solidity snapshot. + function _snapStart() internal { + cachedGas = 1; + cachedGas = gasleft(); + } + + // Internal function to end a Solidity snapshot. + function _snapEnd() internal returns (uint256 gasUsed) { + gasUsed = cachedGas - gasleft() - 138; + cachedGas = 2; + } +} + +contract Flare { + bytes32[] public data; + + function run(uint256 n_) public { + for (uint256 i = 0; i < n_; i++) { + data.push(keccak256(abi.encodePacked(i))); + } + } +} + +contract TargetA { + TargetB public target; + + constructor() { + target = new TargetB(); + } + + function update(uint256 x_) public { + target.update(x_); + } +} + +contract TargetB { + uint256 public x; + + function update(uint256 x_) public { + x = x_; + } +} + +contract TargetC {}