diff --git a/src/common/simulation.rs b/src/common/simulation.rs
index 4a0c4ca8a..1eae161ad 100644
--- a/src/common/simulation.rs
+++ b/src/common/simulation.rs
@@ -238,6 +238,27 @@ where
ViolationOpCode(opcode),
));
}
+
+ for (addr, opcode) in &phase.ext_code_access_info {
+ if *addr == self.entry_point.address() {
+ violations.push(SimulationViolation::UsedForbiddenOpcode(
+ entity,
+ *addr,
+ ViolationOpCode(*opcode),
+ ));
+ }
+ }
+
+ for (addr, size) in &phase.contract_size {
+ if *addr != context.entity_infos.sender_address() && size.contract_size <= 2 {
+ violations.push(SimulationViolation::UsedForbiddenOpcode(
+ entity,
+ *addr,
+ ViolationOpCode(size.opcode),
+ ));
+ }
+ }
+
for precompile in &phase.forbidden_precompiles_used {
let (contract, precompile) = parse_combined_tracer_str(precompile)?;
violations.push(SimulationViolation::UsedForbiddenPrecompile(
diff --git a/src/common/tracer.rs b/src/common/tracer.rs
index ab6cf0b2c..87420094b 100644
--- a/src/common/tracer.rs
+++ b/src/common/tracer.rs
@@ -5,7 +5,7 @@ use std::{
};
use anyhow::Context;
-use ethers::types::{transaction::eip2718::TypedTransaction, Address, BlockId, U256};
+use ethers::types::{transaction::eip2718::TypedTransaction, Address, BlockId, Opcode, U256};
use serde::{de::DeserializeOwned, Deserialize, Serialize};
use super::{
@@ -36,6 +36,8 @@ pub struct Phase {
pub called_non_entry_point_with_value: bool,
pub ran_out_of_gas: bool,
pub undeployed_contract_accesses: Vec
,
+ pub contract_size: HashMap,
+ pub ext_code_access_info: HashMap,
}
#[derive(Clone, Debug, Deserialize, Serialize)]
@@ -45,6 +47,13 @@ pub struct StorageAccess {
pub slots: Vec,
}
+#[derive(Clone, Debug, Deserialize, Serialize)]
+#[serde(rename_all = "camelCase")]
+pub struct ContractSize {
+ pub contract_size: u64,
+ pub opcode: Opcode,
+}
+
#[derive(Clone, Debug, Deserialize, Serialize)]
pub struct AssociatedSlotsByAddress(HashMap>);
diff --git a/tracer/src/validationTracer.ts b/tracer/src/validationTracer.ts
index 117c9d216..77a4b6fff 100644
--- a/tracer/src/validationTracer.ts
+++ b/tracer/src/validationTracer.ts
@@ -23,6 +23,8 @@ interface Phase {
calledNonEntryPointWithValue: boolean;
ranOutOfGas: boolean;
undeployedContractAccesses: string[];
+ contractSize: Record;
+ extCodeAccessInfo: { [addr: string]: string }
}
interface StorageAccess {
@@ -30,6 +32,15 @@ interface StorageAccess {
slots: string[];
}
+interface ContractSize {
+ contractSize: number;
+ opcode: string;
+}
+
+interface RelevantStepData {
+ opcode: string
+ stackTop3: any[]
+}
type InternalPhase = Omit<
Phase,
| "forbiddenOpcodesUsed"
@@ -37,12 +48,14 @@ type InternalPhase = Omit<
| "storageAccesses"
| "addressesCallingWithValue"
| "undeployedContractAccesses"
+ | "contractSize"
> & {
forbiddenOpcodesUsed: StringSet;
forbiddenPrecompilesUsed: StringSet;
storageAccesses: Record;
addressesCallingWithValue: StringSet;
undeployedContractAccesses: StringSet;
+ contractSize: Record;
};
type StringSet = Record;
@@ -108,7 +121,7 @@ type StringSet = Record;
let entryPointAddress = "";
let justCalledGas = false;
let pendingKeccakAddress = "";
- let lastThreeOpcodes: string[] = [];
+ let lastThreeOpcodes: RelevantStepData[] = [];
function newInternalPhase(): InternalPhase {
return {
@@ -120,6 +133,9 @@ type StringSet = Record;
calledNonEntryPointWithValue: false,
ranOutOfGas: false,
undeployedContractAccesses: {},
+ contractSize: {},
+ extCodeAccessInfo: {},
+
};
}
@@ -128,6 +144,8 @@ type StringSet = Record;
calledBannedEntryPointMethod,
calledNonEntryPointWithValue,
ranOutOfGas,
+ contractSize,
+ extCodeAccessInfo,
} = currentPhase;
const forbiddenOpcodesUsed = Object.keys(currentPhase.forbiddenOpcodesUsed);
const forbiddenPrecompilesUsed = Object.keys(
@@ -154,10 +172,11 @@ type StringSet = Record;
calledNonEntryPointWithValue,
ranOutOfGas,
undeployedContractAccesses,
+ contractSize,
+ extCodeAccessInfo,
};
phases.push(phase);
currentPhase = newInternalPhase();
- lastThreeOpcodes = [];
}
function bigIntToNumber(n: BigInt): number {
@@ -237,22 +256,30 @@ type StringSet = Record;
)[keccakResult] = true;
pendingKeccakAddress = "";
}
- const opcode = log.op.toString();
- lastThreeOpcodes.push(opcode);
+ const opcode = log.op.toString()
+
+ const stackSize = log.stack.length()
+ const stackTop3 = []
+ for (let i = 0; i < 3 && i < stackSize; i++) {
+ stackTop3.push(log.stack.peek(i))
+ }
+
+ lastThreeOpcodes.push({ opcode, stackTop3 })
if (lastThreeOpcodes.length > 3) {
- lastThreeOpcodes.shift();
+ lastThreeOpcodes.shift()
}
const entryPointIsExecuting = log.getDepth() === 1;
if (entryPointIsExecuting) {
if (opcode === "NUMBER") {
concludePhase();
- } else if (opcode === "REVERT") {
+ } else if (opcode === "REVERT" || opcode == "RETURN") {
const offset = bigIntToNumber(log.stack.peek(0));
const length = bigIntToNumber(log.stack.peek(1));
revertData = toHex(log.memory.slice(offset, offset + length));
+ lastThreeOpcodes = [];
}
} else {
// The entry point is allowed to freely call `GAS`, but otherwise we
@@ -272,6 +299,19 @@ type StringSet = Record;
}
}
+
+ const lastOpInfo = lastThreeOpcodes[lastThreeOpcodes.length - 2]
+ // store all addresses touched by EXTCODE* opcodes
+ if (lastOpInfo?.opcode?.match(/^(EXT.*)$/) != null) {
+ const addr = toAddress(lastOpInfo.stackTop3[0].toString(16))
+ const addrHex = toHex(addr)
+ const last3opcodesString = lastThreeOpcodes.map(x => x.opcode).join(' ')
+ // only store the last EXTCODE* opcode per address - could even be a boolean for our current use-case
+ if (last3opcodesString.match(/^(\w+) EXTCODESIZE ISZERO$/) == null) {
+ currentPhase.extCodeAccessInfo[addrHex] = opcode
+ }
+ }
+
if (opcode === "CREATE2") {
if (phases.length === 0) {
// In factory phase.
@@ -323,6 +363,14 @@ type StringSet = Record;
const index = EXT_OPCODES[opcode] ? 0 : 1;
const address = toAddress(log.stack.peek(index).toString(16));
const addressHex = toHex(address);
+
+ if (currentPhase.contractSize[addressHex] == null && !PRECOMPILE_WHILTELIST[addressHex]) {
+ currentPhase.contractSize[addressHex] = {
+ contractSize: db.getCode(address).length,
+ opcode
+ }
+ }
+
if (!isPrecompiled(address)) {
if (
!accessedContractAddresses[addressHex] ||