Skip to content

Commit f55cf48

Browse files
HenryMBaldwinhenry-aiclaude
authored andcommitted
fix(rpc): surface StorageOverrideNotPermitted from seismic-evm (#384)
## Summary Companion to [seismic-evm#52](SeismicSystems/seismic-evm#52), which rejects `state` / `stateDiff` overrides at the `apply_account_override` chokepoint to close an audit finding where signed reads could be evaluated against attacker-controlled storage. - Adds `EthApiError::StorageOverrideNotPermitted(Address)` next to the existing `CodeOverrideNotPermitted` variant. - Includes it in the `invalid_params_rpc_err` arm so it surfaces as `-32602` (matching the code-override behavior). - Maps the underlying `StateOverrideError::StorageOverrideNotPermitted` through `From<StateOverrideError> for EthApiError`. - Bumps `alloy-evm` / `alloy-seismic-evm` to the seismic-evm branch tip (`e644240`) so the new variant resolves. ## Merge order **Do not merge this PR until:** 1. [seismic-evm#52](SeismicSystems/seismic-evm#52) is merged to `seismic`. 2. The `alloy-evm` / `alloy-seismic-evm` rev in `Cargo.toml` (lines 794-795) is re-bumped to the **post-merge commit on `seismic`** — the current pin points at the branch commit, which will differ from the merge commit (squash / merge / rebase). ## Test plan - [ ] After dep re-bump: `cargo check -p reth-rpc-eth-types` builds clean for the error module changes. (Note: the base `veridise-audit-april-2026` has pre-existing `Url: serde` errors unrelated to this PR — verified by building the baseline without these changes.) - [ ] Signed `eth_call` with a `stateDiff` override returns `-32602 invalid params` with the storage-override message. --------- Co-authored-by: henry-ai <henrymbaldwin+ai@proton.me> Co-authored-by: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
1 parent e97866d commit f55cf48

4 files changed

Lines changed: 157 additions & 4 deletions

File tree

Cargo.lock

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

Cargo.toml

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -792,5 +792,5 @@ seismic-alloy-provider = { git = "https://github.com/SeismicSystems/seismic-allo
792792
seismic-alloy-genesis = { git = "https://github.com/SeismicSystems/seismic-alloy.git", rev = "c1ce53389ad2bf9bd04c15f4924abe77677395b6" }
793793

794794
# evm
795-
alloy-evm = { git = "https://github.com/SeismicSystems/seismic-evm.git", rev = "c33f3076495d153eefbde07dd81c3a575f3d19b8" }
796-
alloy-seismic-evm = { git = "https://github.com/SeismicSystems/seismic-evm.git", rev = "c33f3076495d153eefbde07dd81c3a575f3d19b8" }
795+
alloy-evm = { git = "https://github.com/SeismicSystems/seismic-evm.git", rev = "0a751067fa55afc762e64c919c4443eea72546ca" }
796+
alloy-seismic-evm = { git = "https://github.com/SeismicSystems/seismic-evm.git", rev = "0a751067fa55afc762e64c919c4443eea72546ca" }

crates/rpc/rpc-eth-types/src/error/mod.rs

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -118,6 +118,9 @@ pub enum EthApiError {
118118
/// Code overrides are not permitted (Seismic privacy)
119119
#[error("code overrides are not permitted on Seismic (account: {0:?})")]
120120
CodeOverrideNotPermitted(Address),
121+
/// Storage overrides are not permitted (Seismic privacy)
122+
#[error("storage overrides are not permitted on Seismic (account: {0:?})")]
123+
StorageOverrideNotPermitted(Address),
121124
/// Other internal error
122125
#[error(transparent)]
123126
Internal(RethError),
@@ -258,6 +261,7 @@ impl From<EthApiError> for jsonrpsee_types::error::ErrorObject<'static> {
258261
EthApiError::Signing(_) |
259262
EthApiError::BothStateAndStateDiffInOverride(_) |
260263
EthApiError::CodeOverrideNotPermitted(_) |
264+
EthApiError::StorageOverrideNotPermitted(_) |
261265
EthApiError::InvalidTracerConfig |
262266
EthApiError::TransactionConversionError |
263267
EthApiError::InvalidRewardPercentiles |
@@ -356,6 +360,9 @@ where
356360
StateOverrideError::CodeOverrideNotPermitted(address) => {
357361
Self::CodeOverrideNotPermitted(address)
358362
}
363+
StateOverrideError::StorageOverrideNotPermitted(address) => {
364+
Self::StorageOverrideNotPermitted(address)
365+
}
359366
StateOverrideError::Database(err) => err.into(),
360367
}
361368
}

crates/seismic/node/tests/e2e/integration.rs

Lines changed: 146 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1390,3 +1390,149 @@ async fn test_usdc_only_susdc_transfer_e2e() -> eyre::Result<()> {
13901390
assert!(receipt.status());
13911391
Ok(())
13921392
}
1393+
1394+
#[tokio::test(flavor = "multi_thread")]
1395+
async fn test_eth_call_rejects_storage_override() -> eyre::Result<()> {
1396+
let (_node, client, chain_id, wallet, _tasks) = setup_test_node().await?;
1397+
1398+
let victim_addr = Address::from_hex("0x0000000000000000000000000000000000001234").unwrap();
1399+
1400+
let storage: alloy_primitives::map::B256HashMap<B256> =
1401+
[(B256::ZERO, B256::from(U256::from(1)))].into_iter().collect();
1402+
1403+
let mut state_overrides = StateOverride::default();
1404+
state_overrides
1405+
.insert(victim_addr, AccountOverride { state_diff: Some(storage), ..Default::default() });
1406+
1407+
let result = EthApiOverrideClient::<Block>::call(
1408+
&client,
1409+
SeismicTransactionRequest {
1410+
inner: TransactionRequest {
1411+
from: Some(wallet.inner.address()),
1412+
to: Some(TxKind::Call(victim_addr)),
1413+
gas: Some(1_000_000),
1414+
chain_id: Some(chain_id),
1415+
..Default::default()
1416+
},
1417+
seismic_elements: None,
1418+
}
1419+
.into(),
1420+
None,
1421+
Some(state_overrides),
1422+
None,
1423+
)
1424+
.await;
1425+
1426+
match &result {
1427+
Ok(output) => panic!(
1428+
"eth_call with storage override should be rejected, but got Ok: 0x{}",
1429+
hex::encode(output)
1430+
),
1431+
Err(e) => {
1432+
let err_msg = e.to_string();
1433+
assert!(
1434+
err_msg.to_lowercase().contains("storage overrides are not permitted"),
1435+
"Expected storage override rejection error, got: {}",
1436+
err_msg
1437+
);
1438+
}
1439+
}
1440+
Ok(())
1441+
}
1442+
1443+
#[tokio::test(flavor = "multi_thread")]
1444+
async fn test_eth_estimate_gas_rejects_storage_override() -> eyre::Result<()> {
1445+
let (_node, client, chain_id, wallet, _tasks) = setup_test_node().await?;
1446+
1447+
let victim_addr = Address::from_hex("0x0000000000000000000000000000000000001234").unwrap();
1448+
1449+
let storage: alloy_primitives::map::B256HashMap<B256> =
1450+
[(B256::ZERO, B256::from(U256::from(1)))].into_iter().collect();
1451+
1452+
let mut state_overrides = StateOverride::default();
1453+
state_overrides
1454+
.insert(victim_addr, AccountOverride { state_diff: Some(storage), ..Default::default() });
1455+
1456+
let result = EthApiOverrideClient::<Block>::estimate_gas(
1457+
&client,
1458+
SeismicCallRequest::TransactionRequest(SeismicTransactionRequest {
1459+
inner: TransactionRequest {
1460+
from: Some(wallet.inner.address()),
1461+
to: Some(TxKind::Call(victim_addr)),
1462+
gas: Some(1_000_000),
1463+
chain_id: Some(chain_id),
1464+
..Default::default()
1465+
},
1466+
seismic_elements: None,
1467+
}),
1468+
None,
1469+
Some(state_overrides),
1470+
)
1471+
.await;
1472+
1473+
match &result {
1474+
Ok(gas) => {
1475+
panic!("eth_estimateGas with storage override should be rejected, but got Ok: {}", gas)
1476+
}
1477+
Err(e) => {
1478+
let err_msg = e.to_string();
1479+
assert!(
1480+
err_msg.to_lowercase().contains("storage overrides are not permitted"),
1481+
"Expected storage override rejection error, got: {}",
1482+
err_msg
1483+
);
1484+
}
1485+
}
1486+
Ok(())
1487+
}
1488+
1489+
#[tokio::test(flavor = "multi_thread")]
1490+
async fn test_eth_simulate_v1_rejects_storage_override() -> eyre::Result<()> {
1491+
let (_node, client, chain_id, wallet, _tasks) = setup_test_node().await?;
1492+
1493+
let victim_addr = Address::from_hex("0x0000000000000000000000000000000000001234").unwrap();
1494+
1495+
let storage: alloy_primitives::map::B256HashMap<B256> =
1496+
[(B256::ZERO, B256::from(U256::from(1)))].into_iter().collect();
1497+
1498+
let mut state_overrides = StateOverride::default();
1499+
state_overrides
1500+
.insert(victim_addr, AccountOverride { state_diff: Some(storage), ..Default::default() });
1501+
1502+
let nonce = get_nonce(&client, wallet.inner.address()).await;
1503+
let tx_bytes = get_signed_deploy_tx_bytes(
1504+
wallet.inner.clone(),
1505+
nonce,
1506+
chain_id,
1507+
ContractTestContext::get_deploy_input_plaintext(),
1508+
)
1509+
.await;
1510+
1511+
let block_with_storage_override = SimBlock {
1512+
block_overrides: None,
1513+
state_overrides: Some(state_overrides),
1514+
calls: vec![SeismicCallRequest::Bytes(tx_bytes)],
1515+
};
1516+
1517+
let simulate_payload = SimulatePayload::<SeismicCallRequest> {
1518+
block_state_calls: vec![block_with_storage_override],
1519+
trace_transfers: false,
1520+
validation: false,
1521+
return_full_transactions: false,
1522+
};
1523+
1524+
let result = EthApiOverrideClient::<Block>::simulate_v1(&client, simulate_payload, None).await;
1525+
1526+
match &result {
1527+
Ok(_) => panic!("eth_simulateV1 with storage override should be rejected"),
1528+
Err(e) => {
1529+
let err_msg = e.to_string();
1530+
assert!(
1531+
err_msg.to_lowercase().contains("storage overrides are not permitted"),
1532+
"Expected storage override rejection error, got: {}",
1533+
err_msg
1534+
);
1535+
}
1536+
}
1537+
Ok(())
1538+
}

0 commit comments

Comments
 (0)