Skip to content

Commit 5eec493

Browse files
authored
Merge branch 'master' into refactor/anvil_txtype_enveloppe_macro
2 parents dcdc20a + bb3266d commit 5eec493

File tree

16 files changed

+364
-196
lines changed

16 files changed

+364
-196
lines changed

Cargo.lock

Lines changed: 85 additions & 69 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -290,9 +290,9 @@ alloy-evm = "0.24.1"
290290
alloy-op-evm = "0.24.1"
291291

292292
# revm
293-
revm = { version = "33.0.0", default-features = false }
293+
revm = { version = "33.1.0", default-features = false }
294294
revm-inspectors = { version = "0.33.0", features = ["serde"] }
295-
op-revm = { version = "14.0.0", default-features = false }
295+
op-revm = { version = "14.1.0", default-features = false }
296296

297297
## cli
298298
anstream = "0.6"
@@ -382,6 +382,7 @@ jiff = { version = "0.2", default-features = false, features = [
382382
heck = "0.5"
383383
uuid = "1.18.1"
384384
flate2 = "1.1"
385+
ethereum_ssz = "0.10"
385386

386387
## Pinned dependencies. Enabled for the workspace in crates/test-utils.
387388

@@ -440,8 +441,8 @@ rexpect = { git = "https://github.com/rust-cli/rexpect", rev = "2ed0b1898d7edaf6
440441
# alloy-op-evm = { git = "https://github.com/alloy-rs/evm.git", rev = "237d0a0" }
441442

442443
## revm
443-
revm = { git = "https://github.com/bluealloy/revm.git", rev = "7e59936" }
444-
op-revm = { git = "https://github.com/bluealloy/revm.git", rev = "7e59936" }
444+
# revm = { git = "https://github.com/bluealloy/revm.git", rev = "7e59936" }
445+
# op-revm = { git = "https://github.com/bluealloy/revm.git", rev = "7e59936" }
445446
# revm-inspectors = { git = "https://github.com/zerosnacks/revm-inspectors.git", rev = "0aaab71" }
446447

447448
## foundry

README.md

Lines changed: 0 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -351,7 +351,6 @@ shall be dual licensed as above, without any additional terms or conditions.
351351
[solady]: https://github.com/Vectorized/solady
352352
[openzeppelin]: https://github.com/OpenZeppelin/openzeppelin-contracts/tree/release-v5.1
353353
[morpho-blue]: https://github.com/morpho-org/morpho-blue
354-
[foundry-compilers]: https://github.com/foundry-rs/compilers
355354
[solmate]: https://github.com/transmissions11/solmate/
356355
[geb]: https://github.com/reflexer-labs/geb
357356
[benchmark-post]: https://www.paradigm.xyz/2022/03/foundry-02#blazing-fast-compilation--testing

crates/anvil/Cargo.toml

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -95,6 +95,7 @@ tempfile.workspace = true
9595
itertools.workspace = true
9696
rand_08.workspace = true
9797
eyre.workspace = true
98+
ethereum_ssz.workspace = true
9899

99100
# cli
100101
clap = { version = "4", features = [

crates/anvil/src/server/beacon_handler.rs

Lines changed: 114 additions & 17 deletions
Original file line numberDiff line numberDiff line change
@@ -9,11 +9,50 @@ use alloy_rpc_types_beacon::{
99
use axum::{
1010
Json,
1111
extract::{Path, Query, State},
12+
http::HeaderMap,
1213
response::{IntoResponse, Response},
1314
};
14-
use hyper::StatusCode;
15+
use ssz::Encode;
1516
use std::{collections::HashMap, str::FromStr as _};
1617

18+
/// Helper function to determine if the Accept header indicates a preference for SSZ (octet-stream)
19+
/// over JSON.
20+
pub fn must_be_ssz(headers: &HeaderMap) -> bool {
21+
headers
22+
.get(axum::http::header::ACCEPT)
23+
.and_then(|v| v.to_str().ok())
24+
.map(|accept_str| {
25+
let mut octet_stream_q = 0.0;
26+
let mut json_q = 0.0;
27+
28+
// Parse each media type in the Accept header
29+
for media_type in accept_str.split(',') {
30+
let media_type = media_type.trim();
31+
let quality = media_type
32+
.split(';')
33+
.find_map(|param| {
34+
let param = param.trim();
35+
if let Some(q) = param.strip_prefix("q=") {
36+
q.parse::<f32>().ok()
37+
} else {
38+
None
39+
}
40+
})
41+
.unwrap_or(1.0); // Default quality factor is 1.0
42+
43+
if media_type.starts_with("application/octet-stream") {
44+
octet_stream_q = quality;
45+
} else if media_type.starts_with("application/json") {
46+
json_q = quality;
47+
}
48+
}
49+
50+
// Prefer octet-stream if it has higher quality factor
51+
octet_stream_q > json_q
52+
})
53+
.unwrap_or(false)
54+
}
55+
1756
/// Handles incoming Beacon API requests for blob sidecars
1857
///
1958
/// This endpoint is deprecated. Use `GET /eth/v1/beacon/blobs/{block_id}` instead.
@@ -32,6 +71,7 @@ pub async fn handle_get_blob_sidecars(
3271
///
3372
/// GET /eth/v1/beacon/blobs/{block_id}
3473
pub async fn handle_get_blobs(
74+
headers: HeaderMap,
3575
State(api): State<EthApi>,
3676
Path(block_id): Path<String>,
3777
Query(versioned_hashes): Query<HashMap<String, String>>,
@@ -50,11 +90,18 @@ pub async fn handle_get_blobs(
5090

5191
// Get the blob sidecars using existing EthApi logic
5292
match api.anvil_get_blobs_by_block_id(block_id, versioned_hashes) {
53-
Ok(Some(blobs)) => (
54-
StatusCode::OK,
55-
Json(GetBlobsResponse { execution_optimistic: false, finalized: false, data: blobs }),
56-
)
57-
.into_response(),
93+
Ok(Some(blobs)) => {
94+
if must_be_ssz(&headers) {
95+
blobs.as_ssz_bytes().into_response()
96+
} else {
97+
Json(GetBlobsResponse {
98+
execution_optimistic: false,
99+
finalized: false,
100+
data: blobs,
101+
})
102+
.into_response()
103+
}
104+
}
58105
Ok(None) => BeaconError::block_not_found().into_response(),
59106
Err(_) => BeaconError::internal_error().into_response(),
60107
}
@@ -67,17 +114,67 @@ pub async fn handle_get_blobs(
67114
/// GET /eth/v1/beacon/genesis
68115
pub async fn handle_get_genesis(State(api): State<EthApi>) -> Response {
69116
match api.anvil_get_genesis_time() {
70-
Ok(genesis_time) => (
71-
StatusCode::OK,
72-
Json(GenesisResponse {
73-
data: GenesisData {
74-
genesis_time,
75-
genesis_validators_root: B256::ZERO,
76-
genesis_fork_version: B32::ZERO,
77-
},
78-
}),
79-
)
80-
.into_response(),
117+
Ok(genesis_time) => Json(GenesisResponse {
118+
data: GenesisData {
119+
genesis_time,
120+
genesis_validators_root: B256::ZERO,
121+
genesis_fork_version: B32::ZERO,
122+
},
123+
})
124+
.into_response(),
81125
Err(_) => BeaconError::internal_error().into_response(),
82126
}
83127
}
128+
#[cfg(test)]
129+
mod tests {
130+
use super::*;
131+
use axum::http::HeaderValue;
132+
133+
fn header_map_with_accept(accept: &str) -> HeaderMap {
134+
let mut headers = HeaderMap::new();
135+
headers.insert(axum::http::header::ACCEPT, HeaderValue::from_str(accept).unwrap());
136+
headers
137+
}
138+
139+
#[test]
140+
fn test_must_be_ssz() {
141+
let test_cases = vec![
142+
(None, false, "no Accept header"),
143+
(Some("application/json"), false, "JSON only"),
144+
(Some("application/octet-stream"), true, "octet-stream only"),
145+
(Some("application/octet-stream;q=1.0,application/json;q=0.9"), true, "SSZ preferred"),
146+
(
147+
Some("application/json;q=1.0,application/octet-stream;q=0.9"),
148+
false,
149+
"JSON preferred",
150+
),
151+
(Some("application/octet-stream;q=0.5,application/json;q=0.5"), false, "equal quality"),
152+
(
153+
Some("text/html;q=0.9, application/octet-stream;q=1.0, application/json;q=0.8"),
154+
true,
155+
"multiple types",
156+
),
157+
(
158+
Some("application/octet-stream ; q=1.0 , application/json ; q=0.9"),
159+
true,
160+
"whitespace handling",
161+
),
162+
(Some("application/octet-stream, application/json;q=0.9"), true, "default quality"),
163+
];
164+
165+
for (accept_header, expected, description) in test_cases {
166+
let headers = match accept_header {
167+
None => HeaderMap::new(),
168+
Some(header) => header_map_with_accept(header),
169+
};
170+
assert_eq!(
171+
must_be_ssz(&headers),
172+
expected,
173+
"Test case '{}' failed: expected {}, got {}",
174+
description,
175+
expected,
176+
!expected
177+
);
178+
}
179+
}
180+
}

crates/anvil/tests/it/beacon_api.rs

Lines changed: 46 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
use crate::utils::http_provider;
2-
use alloy_consensus::{SidecarBuilder, SimpleCoder, Transaction};
2+
use alloy_consensus::{Blob, SidecarBuilder, SimpleCoder, Transaction};
33
use alloy_hardforks::EthereumHardfork;
44
use alloy_network::{TransactionBuilder, TransactionBuilder4844};
55
use alloy_primitives::{B256, FixedBytes, U256, b256};
@@ -8,6 +8,7 @@ use alloy_rpc_types::TransactionRequest;
88
use alloy_rpc_types_beacon::{genesis::GenesisResponse, sidecar::GetBlobsResponse};
99
use alloy_serde::WithOtherFields;
1010
use anvil::{NodeConfig, spawn};
11+
use ssz::Decode;
1112

1213
#[tokio::test(flavor = "multi_thread")]
1314
async fn test_beacon_api_get_blob_sidecars() {
@@ -107,16 +108,59 @@ async fn test_beacon_api_get_blobs() {
107108

108109
let response = client.get(&url).send().await.unwrap();
109110
assert_eq!(response.status(), reqwest::StatusCode::OK);
111+
assert_eq!(
112+
response.headers().get("content-type").and_then(|h| h.to_str().ok()),
113+
Some("application/json"),
114+
"Expected application/json content-type header"
115+
);
110116

111117
let blobs_response: GetBlobsResponse = response.json().await.unwrap();
112-
113118
// Verify response structure
114119
assert!(!blobs_response.execution_optimistic);
115120
assert!(!blobs_response.finalized);
116121

117122
// Verify we have blob data from all transactions
118123
assert_eq!(blobs_response.data.len(), 3, "Expected 3 blobs from 3 transactions");
119124

125+
// Test response with SSZ encoding
126+
let url = format!("{}/eth/v1/beacon/blobs/{}", handle.http_endpoint(), block_number);
127+
let response = client
128+
.get(&url)
129+
.header(axum::http::header::ACCEPT, "application/octet-stream")
130+
.send()
131+
.await
132+
.unwrap();
133+
assert_eq!(response.status(), reqwest::StatusCode::OK);
134+
assert_eq!(
135+
response.headers().get("content-type").and_then(|h| h.to_str().ok()),
136+
Some("application/octet-stream"),
137+
"Expected application/octet-stream content-type header"
138+
);
139+
140+
let body_bytes = response.bytes().await.unwrap();
141+
142+
// Decode the SSZ-encoded blobs in a spawned thread with larger stack to handle recursion
143+
let decoded_blobs = std::thread::Builder::new()
144+
.stack_size(8 * 1024 * 1024) // 8MB stack for SSZ decoding of large blobs
145+
.spawn(move || Vec::<Blob>::from_ssz_bytes(&body_bytes))
146+
.expect("Failed to spawn decode thread")
147+
.join()
148+
.expect("Decode thread panicked")
149+
.expect("Failed to decode SSZ-encoded blobs");
150+
151+
// Verify we got exactly 3 blobs
152+
assert_eq!(
153+
decoded_blobs.len(),
154+
3,
155+
"Expected 3 blobs from SSZ-encoded response, got {}",
156+
decoded_blobs.len()
157+
);
158+
159+
// Verify the decoded blobs match the JSON response blobs
160+
for (i, (decoded, json)) in decoded_blobs.iter().zip(blobs_response.data.iter()).enumerate() {
161+
assert_eq!(decoded, json, "Blob {i} mismatch between SSZ and JSON responses");
162+
}
163+
120164
// Test filtering with versioned_hashes query parameter - single hash
121165
let url = format!(
122166
"{}/eth/v1/beacon/blobs/{}?versioned_hashes={}",

crates/cast/src/cmd/wallet/list.rs

Lines changed: 9 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -58,7 +58,14 @@ impl ListArgs {
5858
|| self.all
5959
|| (!self.ledger && !self.trezor && !self.aws && !self.gcp)
6060
{
61-
let _ = self.list_local_senders();
61+
match self.list_local_senders() {
62+
Ok(()) => {}
63+
Err(e) => {
64+
if !self.all {
65+
sh_err!("{}", e)?;
66+
}
67+
}
68+
}
6269
}
6370

6471
// Create options for multi wallet - ledger, trezor and AWS
@@ -70,6 +77,7 @@ impl ListArgs {
7077
.gcp(self.gcp || (self.all && gcp_env_vars_set()))
7178
.turnkey(self.turnkey || self.all)
7279
.interactives(0)
80+
.interactive(false)
7381
.build()
7482
.expect("build multi wallet");
7583

crates/cheatcodes/src/evm.rs

Lines changed: 4 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -1214,8 +1214,7 @@ fn inner_start_gas_snapshot(
12141214
name: Option<String>,
12151215
) -> Result {
12161216
// Revert if there is an active gas snapshot as we can only have one active snapshot at a time.
1217-
if ccx.state.gas_metering.active_gas_snapshot.is_some() {
1218-
let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone();
1217+
if let Some((group, name)) = &ccx.state.gas_metering.active_gas_snapshot {
12191218
bail!("gas snapshot was already started with group: {group} and name: {name}");
12201219
}
12211220

@@ -1241,10 +1240,9 @@ fn inner_stop_gas_snapshot(
12411240
name: Option<String>,
12421241
) -> Result {
12431242
// If group and name are not provided, use the last snapshot group and name.
1244-
let (group, name) = group.zip(name).unwrap_or_else(|| {
1245-
let (group, name) = ccx.state.gas_metering.active_gas_snapshot.as_ref().unwrap().clone();
1246-
(group, name)
1247-
});
1243+
let (group, name) = group
1244+
.zip(name)
1245+
.unwrap_or_else(|| ccx.state.gas_metering.active_gas_snapshot.clone().unwrap());
12481246

12491247
if let Some(record) = ccx
12501248
.state

crates/cli/src/utils/cmd.rs

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -169,6 +169,7 @@ pub fn has_different_gas_calc(chain_id: u64) -> bool {
169169
| NamedChain::KaruraTestnet
170170
| NamedChain::Mantle
171171
| NamedChain::MantleSepolia
172+
| NamedChain::Monad
172173
| NamedChain::MonadTestnet
173174
| NamedChain::Moonbase
174175
| NamedChain::Moonbeam

0 commit comments

Comments
 (0)