Skip to content

Commit

Permalink
feat(sim): ban access to arbitrum stylus contracts during sim
Browse files Browse the repository at this point in the history
  • Loading branch information
dancoombs committed Jul 8, 2024
1 parent 1118683 commit 459dab6
Show file tree
Hide file tree
Showing 11 changed files with 178 additions and 49 deletions.
6 changes: 6 additions & 0 deletions crates/pool/proto/op_pool/op_pool.proto
Original file line number Diff line number Diff line change
Expand Up @@ -649,6 +649,7 @@ message SimulationViolationError {
InvalidPaymasterSignature invalid_paymaster_signature = 22;
AssociatedStorageDuringDeploy associated_storage_during_deploy = 23;
InvalidTimeRange invalid_time_range = 24;
AccessedUnsupportedContractType accessed_unsupported_contract_type = 25;
}
}

Expand Down Expand Up @@ -764,3 +765,8 @@ message OperationRevert {
message UnknownRevert {
bytes revert_bytes = 1;
}

message AccessedUnsupportedContractType {
string contract_type = 1;
bytes contract_address = 2;
}
32 changes: 25 additions & 7 deletions crates/pool/src/server/remote/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,13 +22,13 @@ use rundler_types::{

use super::protos::{
mempool_error, precheck_violation_error, simulation_violation_error, validation_revert,
AccessedUndeployedContract, AggregatorValidationFailed, AssociatedStorageDuringDeploy,
AssociatedStorageIsAlternateSender, CallGasLimitTooLow, CallHadValue,
CalledBannedEntryPointMethod, CodeHashChanged, DidNotRevert, DiscardedOnInsertError, Entity,
EntityThrottledError, EntityType, EntryPointRevert, ExistingSenderWithInitCode,
FactoryCalledCreate2Twice, FactoryIsNotContract, InvalidAccountSignature,
InvalidPaymasterSignature, InvalidSignature, InvalidStorageAccess, InvalidTimeRange,
MaxFeePerGasTooLow, MaxOperationsReachedError, MaxPriorityFeePerGasTooLow,
AccessedUndeployedContract, AccessedUnsupportedContractType, AggregatorValidationFailed,
AssociatedStorageDuringDeploy, AssociatedStorageIsAlternateSender, CallGasLimitTooLow,
CallHadValue, CalledBannedEntryPointMethod, CodeHashChanged, DidNotRevert,
DiscardedOnInsertError, Entity, EntityThrottledError, EntityType, EntryPointRevert,
ExistingSenderWithInitCode, FactoryCalledCreate2Twice, FactoryIsNotContract,
InvalidAccountSignature, InvalidPaymasterSignature, InvalidSignature, InvalidStorageAccess,
InvalidTimeRange, MaxFeePerGasTooLow, MaxOperationsReachedError, MaxPriorityFeePerGasTooLow,
MempoolError as ProtoMempoolError, MultipleRolesViolation, NotStaked,
OperationAlreadyKnownError, OperationDropTooSoon, OperationRevert, OutOfGas,
PaymasterBalanceTooLow, PaymasterDepositTooLow, PaymasterIsNotContract,
Expand Down Expand Up @@ -640,6 +640,18 @@ impl From<SimulationViolation> for ProtoSimulationViolationError {
),
}
}
SimulationViolation::AccessedUnsupportedContractType(contract_type, address) => {
ProtoSimulationViolationError {
violation: Some(
simulation_violation_error::Violation::AccessedUnsupportedContractType(
AccessedUnsupportedContractType {
contract_type,
contract_address: address.to_proto_bytes(),
},
),
),
}
}
}
}
}
Expand Down Expand Up @@ -800,6 +812,12 @@ impl TryFrom<ProtoSimulationViolationError> for SimulationViolation {
from_bytes(&e.needed)?,
)
}
Some(simulation_violation_error::Violation::AccessedUnsupportedContractType(e)) => {
SimulationViolation::AccessedUnsupportedContractType(
e.contract_type,
from_bytes(&e.contract_address)?,
)
}
None => {
bail!("unknown proto mempool simulation violation")
}
Expand Down
1 change: 1 addition & 0 deletions crates/rpc/src/eth/error.rs
Original file line number Diff line number Diff line change
Expand Up @@ -340,6 +340,7 @@ impl From<SimulationViolation> for EthRpcError {
}
SimulationViolation::UsedForbiddenPrecompile(_, _, _)
| SimulationViolation::AccessedUndeployedContract(_, _)
| SimulationViolation::AccessedUnsupportedContractType(_, _)
| SimulationViolation::CalledBannedEntryPointMethod(_)
| SimulationViolation::CallHadValue(_) => Self::OpcodeViolationMap(value),
SimulationViolation::FactoryCalledCreate2Twice(_) => {
Expand Down
10 changes: 9 additions & 1 deletion crates/sim/src/simulation/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -41,7 +41,7 @@ pub struct ValidationContext<UO> {
pub(crate) struct TracerOutput {
pub(crate) phases: Vec<Phase>,
pub(crate) revert_data: Option<String>,
pub(crate) accessed_contract_addresses: Vec<Address>,
pub(crate) accessed_contracts: HashMap<Address, ContractInfo>,
pub(crate) associated_slots_by_address: AssociatedSlotsByAddress,
pub(crate) factory_called_create2_twice: bool,
pub(crate) expected_storage: ExpectedStorage,
Expand All @@ -60,6 +60,14 @@ pub(crate) struct Phase {
pub(crate) ext_code_access_info: HashMap<Address, Opcode>,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
#[serde(rename_all = "camelCase")]
pub(crate) struct ContractInfo {
pub(crate) header: String,
pub(crate) opcode: Opcode,
pub(crate) length: u64,
}

#[derive(Clone, Debug, Deserialize, Serialize)]
pub(crate) struct AccessInfo {
// slot value, just prior this current operation
Expand Down
77 changes: 70 additions & 7 deletions crates/sim/src/simulation/simulator.rs
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,6 @@
use std::{
collections::{HashMap, HashSet},
marker::PhantomData,
mem,
ops::Deref,
sync::Arc,
};
Expand Down Expand Up @@ -351,6 +350,16 @@ where
}
}

for (address, contract_info) in &tracer_out.accessed_contracts {
if contract_info.header.as_str() == "0xEFF000" {
// All arbitrum stylus contracts start with 0xEFF000
violations.push(SimulationViolation::AccessedUnsupportedContractType(
"Arbitrum Stylus".to_string(),
*address,
));
}
}

if tracer_out.factory_called_create2_twice {
let factory = entity_infos.get(EntityType::Factory);
match factory {
Expand Down Expand Up @@ -402,7 +411,7 @@ where
let aggregator_address = entry_point_out.aggregator_info.map(|info| info.address);
let code_hash_future = utils::get_code_hash(
self.provider.deref(),
mem::take(&mut tracer_out.accessed_contract_addresses),
tracer_out.accessed_contracts.keys().cloned().collect(),
Some(block_id),
);
let aggregator_signature_future = self.validate_aggregator_signature(
Expand Down Expand Up @@ -668,6 +677,7 @@ fn override_infos_staked(eis: &mut EntityInfos, allow_unstaked_addresses: &HashS
mod tests {
use std::str::FromStr;

use context::ContractInfo;
use ethers::types::{Address, BlockId, BlockNumber, Bytes, U256, U64};
use rundler_provider::{AggregatorOut, MockEntryPointV0_6, MockProvider};
use rundler_types::{
Expand Down Expand Up @@ -709,11 +719,32 @@ mod tests {

fn get_test_context() -> ValidationContext<UserOperation> {
let tracer_out = TracerOutput {
accessed_contract_addresses: vec![
Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap(),
Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(),
Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(),
],
accessed_contracts: HashMap::from([
(
Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap(),
ContractInfo {
header: "0x608060".to_string(),
opcode: Opcode::CALL,
length: 32,
}
),
(
Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(),
ContractInfo {
header: "0x608060".to_string(),
opcode: Opcode::CALL,
length: 32,
}
),
(
Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(),
ContractInfo {
header: "0x608060".to_string(),
opcode: Opcode::CALL,
length: 32,
}
),
]),
associated_slots_by_address: serde_json::from_str(r#"
{
"0x0000000000000000000000000000000000000000": [
Expand Down Expand Up @@ -1148,4 +1179,36 @@ mod tests {
let res = simulator.gather_context_violations(&mut context);
assert!(res.unwrap().is_empty());
}

#[tokio::test]
async fn test_accessed_unsupported_contract() {
let (provider, mut ep, mut context_provider) = create_base_config();
ep.expect_address()
.returning(|| Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap());
context_provider
.expect_get_specific_violations()
.return_const(vec![]);

let addr = Address::random();
let mut context = get_test_context();
context.tracer_out.accessed_contracts.insert(
addr,
ContractInfo {
header: "0xEFF000".to_string(),
opcode: Opcode::CALL,
length: 32,
},
);

let simulator = create_simulator(provider, ep, context_provider);
let res = simulator.gather_context_violations(&mut context);

assert_eq!(
res.unwrap(),
vec![SimulationViolation::AccessedUnsupportedContractType(
"Arbitrum Stylus".to_string(),
addr
)]
);
}
}
34 changes: 28 additions & 6 deletions crates/sim/src/simulation/v0_6/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -201,18 +201,40 @@ mod tests {
types::{Address, Bytes, U256},
utils::hex,
};
use rundler_types::{contracts::v0_6::i_entry_point::FailedOp, v0_6::UserOperation};
use rundler_types::{contracts::v0_6::i_entry_point::FailedOp, v0_6::UserOperation, Opcode};
use sim_context::ContractInfo;

use super::*;
use crate::simulation::context::{Phase, TracerOutput};

fn get_test_tracer_output() -> TracerOutput {
TracerOutput {
accessed_contract_addresses: vec![
Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap(),
Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(),
Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(),
],
accessed_contracts: HashMap::from([
(
Address::from_str("0x5ff137d4b0fdcd49dca30c7cf57e578a026d2789").unwrap(),
ContractInfo {
header: "0x608060".to_string(),
opcode: Opcode::CALL,
length: 32,
}
),
(
Address::from_str("0xb856dbd4fa1a79a46d426f537455e7d3e79ab7c4").unwrap(),
ContractInfo {
header: "0x608060".to_string(),
opcode: Opcode::CALL,
length: 32,
}
),
(
Address::from_str("0x8abb13360b87be5eeb1b98647a016add927a136c").unwrap(),
ContractInfo {
header: "0x608060".to_string(),
opcode: Opcode::CALL,
length: 32,
}
),
]),
associated_slots_by_address: serde_json::from_str(r#"
{
"0x0000000000000000000000000000000000000000": [
Expand Down
10 changes: 5 additions & 5 deletions crates/sim/src/simulation/v0_7/context.rs
Original file line number Diff line number Diff line change
Expand Up @@ -346,10 +346,10 @@ impl<T> ValidationContextProvider<T> {
}

// Accessed contracts
let accessed_contract_addresses = tracer_out
let accessed_contracts = tracer_out
.calls_from_entry_point
.iter()
.flat_map(|call| call.contract_size.keys().cloned())
.flat_map(|call| call.contract_info.clone())
.collect();

// Associated slots
Expand Down Expand Up @@ -378,7 +378,7 @@ impl<T> ValidationContextProvider<T> {
Ok(ContextTracerOutput {
phases,
revert_data: None,
accessed_contract_addresses,
accessed_contracts,
associated_slots_by_address: AssociatedSlotsByAddress(associated_slots_by_address),
factory_called_create2_twice,
expected_storage: tracer_out.expected_storage,
Expand Down Expand Up @@ -417,8 +417,8 @@ impl<T> ValidationContextProvider<T> {

let mut forbidden_precompiles_used = vec![];
let mut undeployed_contract_accesses = vec![];
call.contract_size.iter().for_each(|(address, info)| {
if info.contract_size == 0 {
call.contract_info.iter().for_each(|(address, info)| {
if info.length == 0 {
if *address < MAX_PRECOMPILE_ADDRESS {
// [OP-062] - banned precompiles
// The tracer catches any allowed precompiles and does not add them to this list
Expand Down
12 changes: 2 additions & 10 deletions crates/sim/src/simulation/v0_7/tracer.rs
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ use rundler_provider::{Provider, SimulationProvider};
use rundler_types::{v0_7::UserOperation, Opcode};
use serde::Deserialize;

use crate::ExpectedStorage;
use crate::{simulation::context::ContractInfo, ExpectedStorage};

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
Expand All @@ -43,7 +43,7 @@ pub(super) struct TopLevelCallInfo {
pub(super) top_level_target_address: String,
pub(super) opcodes: HashMap<Opcode, u64>,
pub(super) access: HashMap<Address, AccessInfo>,
pub(super) contract_size: HashMap<Address, ContractSizeInfo>,
pub(super) contract_info: HashMap<Address, ContractInfo>,
pub(super) ext_code_access_info: HashMap<Address, Opcode>,
pub(super) oog: Option<bool>,
}
Expand All @@ -55,14 +55,6 @@ pub(super) struct AccessInfo {
pub(super) writes: HashMap<U256, u64>,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase")]
#[allow(unused)]
pub(super) struct ContractSizeInfo {
pub(super) opcode: Opcode,
pub(super) contract_size: u64,
}

#[derive(Clone, Debug, Deserialize)]
#[serde(rename_all = "camelCase", untagged)]
pub(super) enum CallInfo {
Expand Down
20 changes: 15 additions & 5 deletions crates/sim/tracer/src/validationTracerV0_6.ts
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ declare function toWord(s: string | Bytes): Bytes;
interface Output {
phases: Phase[];
revertData: string | null;
accessedContractAddresses: string[];
accessedContracts: Record<string, ContractInfo>;
associatedSlotsByAddress: Record<string, string[]>;
factoryCalledCreate2Twice: boolean;
expectedStorage: Record<string, Record<string, string>>;
Expand Down Expand Up @@ -51,6 +51,12 @@ interface RelevantStepData {
stackEnd: BigInt | null;
}

interface ContractInfo {
opcode: string;
length: number;
header: string;
}

type InternalPhase = Omit<
Phase,
| "forbiddenOpcodesUsed"
Expand Down Expand Up @@ -123,7 +129,7 @@ type StringSet = Record<string, boolean | undefined>;

const phases: Phase[] = [];
let revertData: string | null = null;
const accessedContractAddresses: StringSet = {};
const accessedContracts: Record<string, ContractInfo> = {};
const associatedSlotsByAddressMap: Record<string, StringSet> = {};
const allStorageAccesses: Record<string, Record<string, string | null>> = {};
let factoryCreate2Count = 0;
Expand Down Expand Up @@ -233,7 +239,7 @@ type StringSet = Record<string, boolean | undefined>;
return {
phases,
revertData,
accessedContractAddresses: Object.keys(accessedContractAddresses),
accessedContracts,
associatedSlotsByAddress,
factoryCalledCreate2Twice: factoryCreate2Count > 1,
expectedStorage,
Expand Down Expand Up @@ -371,7 +377,7 @@ type StringSet = Record<string, boolean | undefined>;
const addressHex = toHex(address);
if (!isPrecompiled(address) && !PRECOMPILE_WHITELIST[addressHex]) {
if (
!accessedContractAddresses[addressHex] ||
!accessedContracts[addressHex] ||
currentPhase.undeployedContractAccesses[addressHex]
) {
// The spec says validation must not access code of undeployed
Expand All @@ -386,7 +392,11 @@ type StringSet = Record<string, boolean | undefined>;
delete currentPhase.undeployedContractAccesses[addressHex];
}
}
accessedContractAddresses[addressHex] = true;
accessedContracts[addressHex] = {
header: toHex(db.getCode(address).subarray(0, 3)),
opcode,
length: db.getCode(address).length,
};
} else if (!PRECOMPILE_WHITELIST[addressHex]) {
currentPhase.forbiddenPrecompilesUsed[
getContractCombinedKey(log, addressHex)
Expand Down
Loading

0 comments on commit 459dab6

Please sign in to comment.