diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 69731435bb..4d97b5bfd5 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -25,7 +25,7 @@ jobs: strategy: fail-fast: false matrix: - test-mode: [defaults, race, challenge, stylus] + test-mode: [defaults, race, challenge, stylus, long] steps: - name: Checkout @@ -167,8 +167,14 @@ jobs: if: matrix.test-mode == 'stylus' run: | packages=`go list ./...` - stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=8 -tags=stylustest -run=TestProgramArbitrator > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") + stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=8 -tags=stylustest -run="TestProgramArbitrator" > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") + - name: run long stylus tests + if: matrix.test-mode == 'long' + run: | + packages=`go list ./...` + stdbuf -oL gotestsum --format short-verbose --packages="$packages" --rerun-fails=1 --no-color=false -- ./... -timeout 60m -coverprofile=coverage.txt -covermode=atomic -coverpkg=./...,./go-ethereum/... -parallel=8 -tags=stylustest -run="TestProgramLong" > >(stdbuf -oL tee full.log | grep -vE "INFO|seal") + - name: Archive detailed run log uses: actions/upload-artifact@v3 with: diff --git a/arbitrator/stylus/tests/multicall/Cargo.lock b/arbitrator/stylus/tests/multicall/Cargo.lock index 67b375d746..ca70689bf7 100644 --- a/arbitrator/stylus/tests/multicall/Cargo.lock +++ b/arbitrator/stylus/tests/multicall/Cargo.lock @@ -17,16 +17,29 @@ version = "0.3.3" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "e416903084d3392ebd32d94735c395d6709415b76c7728e594d3f996f2b03e65" dependencies = [ + "alloy-rlp", "bytes", - "cfg-if", + "cfg-if 1.0.0", "const-hex", "derive_more", "hex-literal", "itoa", + "proptest", "ruint", + "serde", "tiny-keccak", ] +[[package]] +name = "alloy-rlp" +version = "0.3.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8d58d9f5da7b40e9bfff0b7e7816700be4019db97d4b6359fe7f94a9e22e42ac" +dependencies = [ + "arrayvec", + "bytes", +] + [[package]] name = "alloy-sol-macro" version = "0.3.1" @@ -51,20 +64,48 @@ dependencies = [ "alloy-primitives", "alloy-sol-macro", "const-hex", + "serde", ] +[[package]] +name = "arrayvec" +version = "0.7.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "96d30a06541fbafbc7f82ed10c06164cfbd2c401138f6addd8404629c4b16711" + [[package]] name = "autocfg" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d468802bab17cbc0cc575e9b053f41e72aa36bfa6b7f55e3529ffa43161b97fa" +[[package]] +name = "bit-set" +version = "0.5.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0700ddab506f33b20a03b13996eccd309a48e5ff77d0d95926aa0210fb4e95f1" +dependencies = [ + "bit-vec", +] + +[[package]] +name = "bit-vec" +version = "0.6.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "349f9b6a179ed607305526ca489b34ad0a41aed5f7980fa90eb03160b69598fb" + [[package]] name = "bitflags" version = "1.3.2" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "bef38d45163c2f1dde094a7dfd33ccf595c92905c8f8f4fdc18d06fb1037718a" +[[package]] +name = "bitflags" +version = "2.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cf4b9d6a944f767f8e5e0db018570623c85f3d925ac718db4e06d0187adb21c1" + [[package]] name = "block-buffer" version = "0.10.4" @@ -86,6 +127,12 @@ version = "1.4.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "89b2fd2a0dcf38d7971e2194b6b6eebab45ae01067456a7fd93d5547a61b70be" +[[package]] +name = "cfg-if" +version = "0.1.10" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4785bdd1c96b2a846b2bd7cc02e86b6b3dbf14e7e53446c4f54c92a361040822" + [[package]] name = "cfg-if" version = "1.0.0" @@ -98,7 +145,7 @@ version = "1.6.1" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "268f52aae268980d03dd9544c1ea591965b2735b038d6998d6e4ab37c8c24445" dependencies = [ - "cfg-if", + "cfg-if 1.0.0", "cpufeatures", "hex", "serde", @@ -184,6 +231,28 @@ version = "1.0.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "56ce8c6da7551ec6c462cbaf3bfbc75131ebbfa1c944aeaa9dab51ca1c5f0c3b" +[[package]] +name = "errno" +version = "0.3.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a258e46cdc063eb8519c00b9fc845fc47bcfca4130e2f08e88665ceda8474245" +dependencies = [ + "libc", + "windows-sys", +] + +[[package]] +name = "fastrand" +version = "2.0.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "658bd65b1cf4c852a3cc96f18a8ce7b5640f6b703f905c7d74532294c2a63984" + +[[package]] +name = "fnv" +version = "1.0.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "3f9eec918d3f24069decb9af1554cad7c880e2da24a9afd88aca000531ab82c1" + [[package]] name = "generic-array" version = "0.14.7" @@ -194,6 +263,17 @@ dependencies = [ "version_check", ] +[[package]] +name = "getrandom" +version = "0.2.14" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "94b22e06ecb0110981051723910cbf0b5f5e09a2062dd7663334ee79a9d1286c" +dependencies = [ + "cfg-if 1.0.0", + "libc", + "wasi", +] + [[package]] name = "heck" version = "0.4.1" @@ -241,9 +321,9 @@ checksum = "e2abad23fbc42b3700f2f279844dc832adb2b2eb069b2df918f455c4e18cc646" [[package]] name = "libc" -version = "0.2.147" +version = "0.2.153" source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "b4668fb0ea861c1df094127ac5f1da3409a82116a4ba74fca2e58ef927159bb3" +checksum = "9c198f91728a82281a64e1f4f9eeb25d82cb32a5de251c6bd1b5154d63a8e7bd" [[package]] name = "libm" @@ -251,18 +331,44 @@ version = "0.2.7" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "f7012b1bbb0719e1097c47611d3898568c546d597c2e74d66f6087edd5233ff4" +[[package]] +name = "linux-raw-sys" +version = "0.4.13" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "01cda141df6706de531b6c46c3a33ecca755538219bd484262fa09410c13539c" + [[package]] name = "memchr" version = "2.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2dffe52ecf27772e601905b7522cb4ef790d2cc203488bbd0e2fe85fcb74566d" +[[package]] +name = "memory_units" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "8452105ba047068f40ff7093dd1d9da90898e63dd61736462e9cdda6a90ad3c3" + +[[package]] +name = "mini-alloc" +version = "0.4.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bd9993556d3850cdbd0da06a3dc81297edcfa050048952d84d75e8b944e8f5af" +dependencies = [ + "cfg-if 1.0.0", + "wee_alloc", +] + [[package]] name = "multicall" version = "0.1.0" dependencies = [ + "alloy-primitives", + "alloy-sol-types", "hex", + "mini-alloc", "stylus-sdk", + "wee_alloc", ] [[package]] @@ -296,15 +402,26 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4e35c06b98bf36aba164cc17cb25f7e232f5c4aeea73baa14b8a9f0d92dbfa65" dependencies = [ - "bitflags", + "bit-set", + "bitflags 1.3.2", "byteorder", + "lazy_static", "num-traits", "rand", "rand_chacha", "rand_xorshift", + "regex-syntax 0.6.29", + "rusty-fork", + "tempfile", "unarray", ] +[[package]] +name = "quick-error" +version = "1.2.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a1d01941d82fa2ab50be1e79e6714289dd7cde78eba4c074bc5a4374f650dfe0" + [[package]] name = "quote" version = "1.0.29" @@ -320,6 +437,8 @@ version = "0.8.5" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "34af8d1a0e25924bc5b7c43c079c942339d8f0a8b57c39049bef581b46327404" dependencies = [ + "libc", + "rand_chacha", "rand_core", ] @@ -338,6 +457,9 @@ name = "rand_core" version = "0.6.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "ec0be4795e2f6a28069bec0b5ff3e2ac9bafc99e6a9a7dc3547996c5c816922c" +dependencies = [ + "getrandom", +] [[package]] name = "rand_xorshift" @@ -357,7 +479,7 @@ dependencies = [ "aho-corasick", "memchr", "regex-automata", - "regex-syntax", + "regex-syntax 0.7.4", ] [[package]] @@ -368,9 +490,15 @@ checksum = "b7b6d6190b7594385f61bd3911cd1be99dfddcfc365a4160cc2ab5bff4aed294" dependencies = [ "aho-corasick", "memchr", - "regex-syntax", + "regex-syntax 0.7.4", ] +[[package]] +name = "regex-syntax" +version = "0.6.29" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f162c6dd7b008981e4d40210aca20b4bd0f9b60ca9271061b07f78537722f2e1" + [[package]] name = "regex-syntax" version = "0.7.4" @@ -406,6 +534,31 @@ dependencies = [ "semver", ] +[[package]] +name = "rustix" +version = "0.38.32" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "65e04861e65f21776e67888bfbea442b3642beaa0138fdb1dd7a84a52dffdb89" +dependencies = [ + "bitflags 2.5.0", + "errno", + "libc", + "linux-raw-sys", + "windows-sys", +] + +[[package]] +name = "rusty-fork" +version = "0.3.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "cb3dcc6e454c328bb824492db107ab7c0ae8fcffe4ad210136ef014458c1bc4f" +dependencies = [ + "fnv", + "quick-error", + "tempfile", + "wait-timeout", +] + [[package]] name = "semver" version = "1.0.17" @@ -417,6 +570,20 @@ name = "serde" version = "1.0.171" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "30e27d1e4fd7659406c492fd6cfaf2066ba8773de45ca75e855590f856dc34a9" +dependencies = [ + "serde_derive", +] + +[[package]] +name = "serde_derive" +version = "1.0.171" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "389894603bd18c46fa56231694f8d827779c0951a667087194cf9de94ed24682" +dependencies = [ + "proc-macro2", + "quote", + "syn 2.0.25", +] [[package]] name = "sha3" @@ -434,7 +601,7 @@ version = "0.4.2" dependencies = [ "alloy-primitives", "alloy-sol-types", - "cfg-if", + "cfg-if 1.0.0", "convert_case 0.6.0", "lazy_static", "proc-macro2", @@ -451,7 +618,7 @@ version = "0.4.2" dependencies = [ "alloy-primitives", "alloy-sol-types", - "cfg-if", + "cfg-if 1.0.0", "derivative", "hex", "keccak-const", @@ -492,6 +659,18 @@ dependencies = [ "syn 2.0.25", ] +[[package]] +name = "tempfile" +version = "3.10.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "85b77fafb263dd9d05cbeac119526425676db3784113aa9295c88498cbf8bff1" +dependencies = [ + "cfg-if 1.0.0", + "fastrand", + "rustix", + "windows-sys", +] + [[package]] name = "tiny-keccak" version = "2.0.2" @@ -537,6 +716,121 @@ version = "0.9.4" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "49874b5167b65d7193b8aba1567f5c7d93d001cafc34600cee003eda787e483f" +[[package]] +name = "wait-timeout" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9f200f5b12eb75f8c1ed65abd4b2db8a6e1b138a20de009dacee265a2498f3f6" +dependencies = [ + "libc", +] + +[[package]] +name = "wasi" +version = "0.11.0+wasi-snapshot-preview1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9c8d87e72b64a3b4db28d11ce29237c246188f4f51057d65a7eab63b7987e423" + +[[package]] +name = "wee_alloc" +version = "0.4.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "dbb3b5a6b2bb17cb6ad44a2e68a43e8d2722c997da10e928665c72ec6c0a0b8e" +dependencies = [ + "cfg-if 0.1.10", + "libc", + "memory_units", + "winapi", +] + +[[package]] +name = "winapi" +version = "0.3.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5c839a674fcd7a98952e593242ea400abe93992746761e38641405d28b00f419" +dependencies = [ + "winapi-i686-pc-windows-gnu", + "winapi-x86_64-pc-windows-gnu", +] + +[[package]] +name = "winapi-i686-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ac3b87c63620426dd9b991e5ce0329eff545bccbbb34f3be09ff6fb6ab51b7b6" + +[[package]] +name = "winapi-x86_64-pc-windows-gnu" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "712e227841d057c1ee1cd2fb22fa7e5a5461ae8e48fa2ca79ec42cfc1931183f" + +[[package]] +name = "windows-sys" +version = "0.52.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "282be5f36a8ce781fad8c8ae18fa3f9beff57ec1b52cb3de0789201425d9a33d" +dependencies = [ + "windows-targets", +] + +[[package]] +name = "windows-targets" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7dd37b7e5ab9018759f893a1952c9420d060016fc19a472b4bb20d1bdd694d1b" +dependencies = [ + "windows_aarch64_gnullvm", + "windows_aarch64_msvc", + "windows_i686_gnu", + "windows_i686_msvc", + "windows_x86_64_gnu", + "windows_x86_64_gnullvm", + "windows_x86_64_msvc", +] + +[[package]] +name = "windows_aarch64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bcf46cf4c365c6f2d1cc93ce535f2c8b244591df96ceee75d8e83deb70a9cac9" + +[[package]] +name = "windows_aarch64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "da9f259dd3bcf6990b55bffd094c4f7235817ba4ceebde8e6d11cd0c5633b675" + +[[package]] +name = "windows_i686_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b474d8268f99e0995f25b9f095bc7434632601028cf86590aea5c8a5cb7801d3" + +[[package]] +name = "windows_i686_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "1515e9a29e5bed743cb4415a9ecf5dfca648ce85ee42e15873c3cd8610ff8e02" + +[[package]] +name = "windows_x86_64_gnu" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "5eee091590e89cc02ad514ffe3ead9eb6b660aedca2183455434b93546371a03" + +[[package]] +name = "windows_x86_64_gnullvm" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "77ca79f2451b49fa9e2af39f0747fe999fcda4f5e241b2898624dca97a1f2177" + +[[package]] +name = "windows_x86_64_msvc" +version = "0.52.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "32b752e52a2da0ddfbdbcc6fceadfeede4c939ed16d13e648833a61dfb611ed8" + [[package]] name = "zeroize" version = "1.6.0" diff --git a/arbitrator/stylus/tests/multicall/Cargo.toml b/arbitrator/stylus/tests/multicall/Cargo.toml index 2ab5728d12..3bc48c6826 100644 --- a/arbitrator/stylus/tests/multicall/Cargo.toml +++ b/arbitrator/stylus/tests/multicall/Cargo.toml @@ -4,8 +4,12 @@ version = "0.1.0" edition = "2021" [dependencies] +alloy-primitives = "0.3.1" +alloy-sol-types = "0.3.1" +mini-alloc = "0.4.2" stylus-sdk = { path = "../../../langs/rust/stylus-sdk", features = ["reentrant"] } hex = "0.4.3" +wee_alloc = "0.4.5" [profile.release] codegen-units = 1 diff --git a/arbitrator/stylus/tests/multicall/src/main.rs b/arbitrator/stylus/tests/multicall/src/main.rs index 1f255cd99c..fd6929b8f1 100644 --- a/arbitrator/stylus/tests/multicall/src/main.rs +++ b/arbitrator/stylus/tests/multicall/src/main.rs @@ -3,13 +3,28 @@ #![no_main] +extern crate alloc; + use stylus_sdk::{ + storage::{StorageCache, GlobalStorage}, alloy_primitives::{Address, B256}, + alloy_sol_types::sol, call::RawCall, console, + evm, prelude::*, }; +use wee_alloc::WeeAlloc; + +#[global_allocator] +static ALLOC: WeeAlloc = WeeAlloc::INIT; + +sol!{ + event Called(address addr, uint8 count, bool success, bytes return_data); + event Storage(bytes32 slot, bytes32 data, bool write); +} + #[entrypoint] fn user_main(input: Vec) -> Result, Vec> { let mut input = input.as_slice(); @@ -19,7 +34,7 @@ fn user_main(input: Vec) -> Result, Vec> { // combined output of all calls let mut output = vec![]; - console!("Calling {count} contract(s)"); + console!("Performing {count} action(s)"); for _ in 0..count { let length = u32::from_be_bytes(input[..4].try_into().unwrap()) as usize; input = &input[4..]; @@ -30,35 +45,75 @@ fn user_main(input: Vec) -> Result, Vec> { let kind = curr[0]; curr = &curr[1..]; - let mut value = None; - if kind == 0 { - value = Some(B256::try_from(&curr[..32]).unwrap()); - curr = &curr[32..]; - } + if kind & 0xf0 == 0 { + // caller + let mut value = None; + if kind & 0x3 == 0 { + value = Some(B256::try_from(&curr[..32]).unwrap()); + curr = &curr[32..]; + }; - let addr = Address::try_from(&curr[..20]).unwrap(); - let data = &curr[20..]; - match value { - Some(value) if !value.is_zero() => console!( - "Calling {addr} with {} bytes and value {} {kind}", - data.len(), - hex::encode(value) - ), - _ => console!("Calling {addr} with {} bytes {kind}", curr.len()), - } + let addr = Address::try_from(&curr[..20]).unwrap(); + let data = &curr[20..]; + match value { + Some(value) if !value.is_zero() => console!( + "Calling {addr} with {} bytes and value {} {kind}", + data.len(), + hex::encode(value) + ), + _ => console!("Calling {addr} with {} bytes {kind}", curr.len()), + } - let raw_call = match kind { - 0 => RawCall::new_with_value(value.unwrap_or_default().into()), - 1 => RawCall::new_delegate(), - 2 => RawCall::new_static(), - x => panic!("unknown call kind {x}"), - }; - let return_data = unsafe { raw_call.call(addr, data)? }; - - if !return_data.is_empty() { - console!("Contract {addr} returned {} bytes", return_data.len()); + let raw_call = match kind & 0x3 { + 0 => RawCall::new_with_value(value.unwrap_or_default().into()), + 1 => RawCall::new_delegate(), + 2 => RawCall::new_static(), + x => panic!("unknown call kind {x}"), + }; + let (success, return_data) = match unsafe { raw_call.call(addr, data) } { + Ok(return_data) => (true, return_data), + Err(revert_data) => { + if kind & 0x4 == 0 { + return Err(revert_data) + } + (false, vec![]) + }, + }; + + if !return_data.is_empty() { + console!("Contract {addr} returned {} bytes", return_data.len()); + } + if kind & 0x8 != 0 { + evm::log(Called { addr, count, success, return_data: return_data.clone() }) + } + output.extend(return_data); + } else if kind & 0xf0 == 0x10 { + // storage + let slot = B256::try_from(&curr[..32]).unwrap(); + curr = &curr[32..]; + let data; + let write; + if kind & 0x7 == 0 { + console!("writing slot {}", curr.len()); + data = B256::try_from(&curr[..32]).unwrap(); + write = true; + unsafe { StorageCache::set_word(slot.into(), data.into()) }; + StorageCache::flush(); + } else if kind & 0x7 == 1{ + console!("reading slot"); + write = false; + data = StorageCache::get_word(slot.into()); + output.extend(data.clone()); + } else { + panic!("unknown storage kind {kind}") + } + if kind & 0x8 != 0 { + console!("slot: {}, data: {}, write {write}", slot, data); + evm::log(Storage { slot: slot.into(), data: data.into(), write }) + } + } else { + panic!("unknown action {kind}") } - output.extend(return_data); input = next; } diff --git a/contracts b/contracts index 77ee9de042..7a41cd59cd 160000 --- a/contracts +++ b/contracts @@ -1 +1 @@ -Subproject commit 77ee9de042de225fab560096f7624f3d13bd12cb +Subproject commit 7a41cd59cdf2eb01cf31c2351b8d1ff6fbf52178 diff --git a/system_tests/program_recursive_test.go b/system_tests/program_recursive_test.go new file mode 100644 index 0000000000..d4cab510d3 --- /dev/null +++ b/system_tests/program_recursive_test.go @@ -0,0 +1,195 @@ +package arbtest + +import ( + "testing" + "time" + + "github.com/ethereum/go-ethereum/common" + "github.com/ethereum/go-ethereum/core/types" + "github.com/ethereum/go-ethereum/core/vm" + "github.com/ethereum/go-ethereum/log" + "github.com/offchainlabs/nitro/arbnode" + "github.com/offchainlabs/nitro/arbutil" + "github.com/offchainlabs/nitro/solgen/go/mocksgen" + "github.com/offchainlabs/nitro/util/testhelpers" +) + +type multiCallRecurse struct { + Name string + opcode vm.OpCode +} + +func printRecurse(recurse []multiCallRecurse) string { + result := "" + for _, contract := range recurse { + result = result + "(" + contract.Name + "," + contract.opcode.String() + ")" + } + return result +} + +func testProgramRecursiveCall(t *testing.T, builder *NodeBuilder, slotVals map[string]common.Hash, rander *testhelpers.PseudoRandomDataSource, recurse []multiCallRecurse) uint64 { + ctx := builder.ctx + slot := common.HexToHash("0x11223344556677889900aabbccddeeff") + val := common.Hash{} + args := []byte{} + if recurse[0].opcode == vm.SSTORE { + // send event from storage on sstore + val = rander.GetHash() + args = append([]byte{0x1, 0, 0, 0, 65, 0x18}, slot[:]...) + args = append(args, val[:]...) + } else if recurse[0].opcode == vm.SLOAD { + args = append([]byte{0x1, 0, 0, 0, 33, 0x11}, slot[:]...) + } else { + t.Fatal("first level must be sload or sstore") + } + shouldSucceed := true + delegateChangesStorageDest := true + storageDest := recurse[0].Name + for i := 1; i < len(recurse); i++ { + call := recurse[i] + prev := recurse[i-1] + args = argsForMulticall(call.opcode, builder.L2Info.GetAddress(prev.Name), nil, args) + if call.opcode == vm.STATICCALL && recurse[0].opcode == vm.SSTORE { + shouldSucceed = false + } + if delegateChangesStorageDest && call.opcode == vm.DELEGATECALL { + storageDest = call.Name + } else { + delegateChangesStorageDest = false + } + } + if recurse[0].opcode == vm.SLOAD { + // send event from caller on sload + args[5] = args[5] | 0x8 + } + multiCaller, err := mocksgen.NewMultiCallTest(builder.L2Info.GetAddress(recurse[len(recurse)-1].Name), builder.L2.Client) + Require(t, err) + ownerTransact := builder.L2Info.GetDefaultTransactOpts("Owner", ctx) + ownerTransact.GasLimit = 10000000 + tx, err := multiCaller.Fallback(&ownerTransact, args) + Require(t, err) + receipt, err := WaitForTx(ctx, builder.L2.Client, tx.Hash(), time.Second*3) + Require(t, err) + + if shouldSucceed { + if receipt.Status != types.ReceiptStatusSuccessful { + log.Error("error when shouldn't", "case", printRecurse(recurse)) + Fatal(t, arbutil.DetailTxError(ctx, builder.L2.Client, tx, receipt)) + } + if len(receipt.Logs) != 1 { + Fatal(t, "incorrect number of logs: ", len(receipt.Logs)) + } + if recurse[0].opcode == vm.SSTORE { + slotVals[storageDest] = val + storageEvt, err := multiCaller.ParseStorage(*receipt.Logs[0]) + Require(t, err) + gotData := common.BytesToHash(storageEvt.Data[:]) + gotSlot := common.BytesToHash(storageEvt.Slot[:]) + if gotData != val || gotSlot != slot || storageEvt.Write != (recurse[0].opcode == vm.SSTORE) { + Fatal(t, "unexpected event", gotData, val, gotSlot, slot, storageEvt.Write, recurse[0].opcode) + } + } else { + calledEvt, err := multiCaller.ParseCalled(*receipt.Logs[0]) + Require(t, err) + gotData := common.BytesToHash(calledEvt.ReturnData) + if gotData != slotVals[storageDest] { + Fatal(t, "unexpected event", gotData, val, slotVals[storageDest]) + } + } + } else if receipt.Status == types.ReceiptStatusSuccessful { + Fatal(t, "should have failed") + } + for contract, expected := range slotVals { + found, err := builder.L2.Client.StorageAt(ctx, builder.L2Info.GetAddress(contract), slot, receipt.BlockNumber) + Require(t, err) + foundHash := common.BytesToHash(found) + if expected != foundHash { + Fatal(t, "contract", contract, "expected", expected, "found", foundHash) + } + } + return receipt.BlockNumber.Uint64() +} + +func testProgramResursiveCalls(t *testing.T, tests [][]multiCallRecurse, jit bool) { + builder, auth, cleanup := setupProgramTest(t, jit) + ctx := builder.ctx + l2client := builder.L2.Client + defer cleanup() + + // set-up contracts + callsAddr := deployWasm(t, ctx, auth, l2client, rustFile("multicall")) + builder.L2Info.SetContract("multicall-rust", callsAddr) + multiCallWasm, _ := readWasmFile(t, rustFile("multicall")) + auth.GasLimit = 32000000 // skip gas estimation + multicallB := deployContract(t, ctx, auth, l2client, multiCallWasm) + builder.L2Info.SetContract("multicall-rust-b", multicallB) + multiAddr, tx, _, err := mocksgen.DeployMultiCallTest(&auth, builder.L2.Client) + builder.L2Info.SetContract("multicall-evm", multiAddr) + Require(t, err) + _, err = EnsureTxSucceeded(ctx, builder.L2.Client, tx) + Require(t, err) + slotVals := make(map[string]common.Hash) + rander := testhelpers.NewPseudoRandomDataSource(t, 0) + + // set-up validator + validatorConfig := arbnode.ConfigDefaultL1NonSequencerTest() + validatorConfig.BlockValidator.Enable = true + AddDefaultValNode(t, ctx, validatorConfig, jit, "") + valClient, valCleanup := builder.Build2ndNode(t, &SecondNodeParams{nodeConfig: validatorConfig}) + defer valCleanup() + + // store initial values + for _, contract := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + storeRecure := []multiCallRecurse{ + { + Name: contract, + opcode: vm.SSTORE, + }, + } + testProgramRecursiveCall(t, builder, slotVals, rander, storeRecure) + } + + // execute transactions + blockNum := uint64(0) + for { + item := int(rander.GetUint64()/4) % len(tests) + blockNum = testProgramRecursiveCall(t, builder, slotVals, rander, tests[item]) + tests[item] = tests[len(tests)-1] + tests = tests[:len(tests)-1] + if len(tests)%100 == 0 { + log.Error("running transactions..", "blockNum", blockNum, "remaining", len(tests)) + } + if len(tests) == 0 { + break + } + } + + // wait for validation + for { + got := valClient.ConsensusNode.BlockValidator.WaitForPos(t, ctx, arbutil.MessageIndex(blockNum), time.Second*10) + if got { + break + } + log.Error("validating blocks..", "waiting for", blockNum, "validated", valClient.ConsensusNode.BlockValidator.GetValidated()) + } +} + +func TestProgramCallSimple(t *testing.T) { + tests := [][]multiCallRecurse{ + { + { + Name: "multicall-rust", + opcode: vm.SLOAD, + }, + { + Name: "multicall-rust", + opcode: vm.STATICCALL, + }, + { + Name: "multicall-rust", + opcode: vm.DELEGATECALL, + }, + }, + } + testProgramResursiveCalls(t, tests, true) +} diff --git a/system_tests/stylus_test.go b/system_tests/stylus_test.go index 46a9103b04..97f3041196 100644 --- a/system_tests/stylus_test.go +++ b/system_tests/stylus_test.go @@ -8,6 +8,8 @@ package arbtest import ( "testing" + + "github.com/ethereum/go-ethereum/core/vm" ) func TestProgramArbitratorKeccak(t *testing.T) { @@ -67,3 +69,42 @@ func TestProgramArbitratorActivateFails(t *testing.T) { func TestProgramArbitratorEarlyExit(t *testing.T) { testEarlyExit(t, false) } + +func fullRecurseTest() [][]multiCallRecurse { + result := make([][]multiCallRecurse, 0) + for _, op0 := range []vm.OpCode{vm.SSTORE, vm.SLOAD} { + for _, contract0 := range []string{"multicall-rust", "multicall-evm"} { + for _, op1 := range []vm.OpCode{vm.CALL, vm.STATICCALL, vm.DELEGATECALL} { + for _, contract1 := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + for _, op2 := range []vm.OpCode{vm.CALL, vm.STATICCALL, vm.DELEGATECALL} { + for _, contract2 := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + for _, op3 := range []vm.OpCode{vm.CALL, vm.STATICCALL, vm.DELEGATECALL} { + for _, contract3 := range []string{"multicall-rust", "multicall-rust-b", "multicall-evm"} { + recurse := make([]multiCallRecurse, 4) + recurse[0].opcode = op0 + recurse[0].Name = contract0 + recurse[1].opcode = op1 + recurse[1].Name = contract1 + recurse[2].opcode = op2 + recurse[2].Name = contract2 + recurse[3].opcode = op3 + recurse[3].Name = contract3 + result = append(result, recurse) + } + } + } + } + } + } + } + } + return result +} + +func TestProgramLongCall(t *testing.T) { + testProgramResursiveCalls(t, fullRecurseTest(), true) +} + +func TestProgramLongArbitratorCall(t *testing.T) { + testProgramResursiveCalls(t, fullRecurseTest(), false) +}