Imagine you're about to swap a significant amount of ETH for USDT on a decentralized exchange (DEX). You use your wallet's simulation feature, and it shows you'll receive 1000 USDT, a good rate! You sign and send. But by the time your transaction hits the chain, a large trade has shifted the pool's reserves, and your transaction either reverts or nets you only 950 USDT. This frustrating (and potentially costly) scenario highlights a critical flaw: simulations offer a snapshot, but the blockchain is a moving target.
The security of your blockchain interactions increasingly depends on validating these simulation outcomes. However, the core issue is their inability to enforce state consistency between the simulation and the actual execution. This gap can lead to vulnerabilities, especially when key contract states, like proxy implementations or DEX reserves, change unexpectedly.
This problem only gets worse now that Pectra has enabled multiple batch of calls in a single transaction making simulations far more complex.
The central challenge is state divergence: critical smart contract states can change organically or adversarially between the time you simulate your transaction and when it's actually mined on the blockchain.
s0 = (calldata, contracts, state0) s1 = (calldata, contracts, state1) exec(s0) != exec(s1)
Several scenarios illustrate this risk:
1. Proxy Upgrade: Imagine you're interacting with an NFT marketplace that uses a proxy contract (allowing logic upgrades). You simulate listing an NFT. Between simulation and execution, if the marketplace upgrades its implementation contract, new logic might interpret your data differently or change fees, leading to unexpected outcomes like lost funds or incorrect listings.
2. DEX Reserve Shift: You simulate swapping 1 ETH for USDT on Uniswap, expecting 3000 USDT based on current reserves. If a large trade occurs just before your transaction, shifting pool reserves, your 1 ETH might yield only 2950 USDT, or the transaction could revert due to slippage.
3. Oracle Value Update: Many DeFi protocols use oracles for price feeds (e.g., ETH/USD). You simulate borrowing against collateral, and the oracle price at simulation time is favorable. If the oracle updates with a lower price for your collateral right before execution, due to market volatility or manipulation, your transaction might fail or put you at immediate risk of liquidation.
Wallets and security tools are increasingly relying on transaction simulations to protect users. By previewing the outcome of complex smart contract interactions, simulations aim to prevent users from signing malicious transactions. However, this reliance introduces its own set of challenges and potential vulnerabilities.
A white paper by Zengo funded by the Ethereum Foundation highlights critical limitations. While simulation is a valuable tool, it's not foolproof. Attackers can exploit both theoretical constraints and practical implementation issues. One notable vulnerability is the "Red Pill Attack," where a malicious contract detects it's running in a simulated environment. It behaves benignly during the simulation, lulling the user into a false sense of security, but then executes its malicious payload once on the actual blockchain. This deception can lead users to approve harmful transactions they believe are safe.
This underscores that while simulations enhance security, they can also create a deceptive safety net if their limitations are not understood and addressed. The integrity of the simulation environment itself becomes a target for sophisticated attackers.
To bridge this simulation-to-execution gap more robustly, we propose that transactions carry not just their execution data (the "what to do") but also a compact set of state constraints (the "under these conditions"). These constraints act as on-chain guards: the transaction asserts them before proceeding, reverting early if the underlying assumptions from your simulation no longer hold.
These constraints can be written using a Domain Specific Language (DSL) designed to be expressive yet simple:
Constraint | Description |
---|---|
TokenAddress.balanceOf($AccountAddress) >= 10000 | checks an expected token balance. |
ProxyAddress.implementation() == $ExpectedImplementationAddress | verifies a proxy's logic hasn't changed. |
block.timestamp < 1720000000 | acts as a soft deadline for time-sensitive transactions. |
storageSlot($ContractAddress, $StorageSlot) == $ExpectedValue | ensures a specific storage slot remains unchanged, more on this later! |
At runtime, these constraint checks are performed first. If all pass, the main transaction logic proceeds. If any fails, the entire transaction reverts, preventing unintended outcomes.
Let's walk through how this works, using our DEX trade and proxy upgrade examples.
1. Transaction Simulation & Intent Representation
When you initiate a transaction, your wallet software performs a simulation. Crucially, the tooling also identifies key blockchain state your transaction depends on like storages, environment, balances, nonces, etc. These are translated into conditions using a DSL.
• DeFi Trade Example: Swap 1 ETH for at least 2950 USDT. Simulation shows 3000 USDT based on current reserves.
• Proxy Upgrade Example: List an NFT for 5 ETH. Simulation uses marketplace implementation $GOOD_CONTRACT_V1
.
2. Extracting the State Constraints
The simulation engine analyzes relied-upon state and extracts these as explicit constraints.
• DeFi Trade Example DSL:
// Preconditions: Ensure DEX pool state is as expected 900 < WETH.balanceOf($PairAddress) < 1100 2,900,000 < USDT.balanceOf($PairAddress) < 3,100,000 // Postcondition: Ensure user receives minimum USDT USDT.balanceOf($UserAddress) >= $oldBalanceOfUSDT + 3000
• Proxy Upgrade Example Constraint:
// Preconditions: Ensure proxy implementation matches the simulation MarketProxyAddress.implementation() == $GOOD_CONTRACT_V1
3. Transaction Packaging
The final transaction, with constraints, is packaged using a multicall provided by the users' wallet (ERC-4337 UserOperation) or delegating to a contract (EIP-7702). This effectively structures the transaction as:
[Constraint Pre-checks] -> [Original Transaction Logic] -> [Constraint Post-checks (optional)]
4. Signing the Enhanced Transaction
The user can now simulate the transaction in different settings. The outcome will either stay true to user expectations or revert if the on-chain conditions no longer match the simulation. The user sees a simulation outcome, similar to what wallets show, but this one can be trusted because the constraints are bundled with the transaction. This entire package, including the constraints, is presented for review before the user signs.
5. Constraint Enforcement On-Chain
When executed: pre-checks compare current on-chain state against your constraints. For example, if DEX reserves shifted too much or the proxy changed, a constraint fails. If any violation occurs, the transaction reverts before main logic runs. If all pass, main logic executes, followed by post-condition checks.
This approach of embedding state constraints becomes particularly powerful and user-friendly due to recent Ethereum improvements, offering several key advantages:
• Atomicity and Improved User Experience: The Pectra upgrade, through EIP-7702, makes Smart Account functionalities accessible to EOAs through delegation. This allows bundling multiple calls (constraint checks and main actions) into one atomic transaction, ensuring all-or-nothing execution with a single signature.
• Backward Compatibility: This system works with existing smart contracts. The constraint checking logic lives in a separate contract that only requires an extra call with the constraints to check.
• Cost Efficiency: Reading storage or calling view
functions multiple times within the same transaction is relatively cheap. The gas for these checks is often a small price for significantly enhanced security.
• Granular Control: You can enforce exact matches for critical state (like a proxy's implementation) and acceptable ranges for volatile state (like DEX reserves within slippage tolerance).
Currently, state checks often rely on public getter
functions. This is limited because the contract must expose the state via a truthful getter, and cross-contract calls can be gas-intensive. For example, if a proxy doesn't expose the implementation() function, we can't check it!
For truly robust, universal, and gas-efficient enforcement, direct, low-level access to read raw storage from other contracts is needed: The concept of an EXTSLOAD opcode as previously discussed in this EIP would tackle all these challenges.
Alternatively, standardized interfaces like IExtsload
(e.g., in UniswapX/Uniswap v4 research) also offer gas-efficient batch state reads:
interface IExtsload { function extsload(bytes32 slot) external view returns (bytes32 value); function extsload(bytes32 startSlot, uint256 nSlots) external view returns (bytes32[] memory values); function extsload(bytes32[] calldata slots) external view returns (bytes32[] memory values); }
While still a contract call, standardization would improve efficiency. Native EVM support would be the ultimate goal.
Enforcing transaction simulation integrity is crucial for user safety in the dynamic world of blockchain.
• State divergence between simulation and execution is critical.
• Attaching explicit state constraints to transactions acts as an on-chain safeguard, ensuring assumptions hold.
• EIP-7702, ERC-4337 and EIP-5792 provide the ideal framework for bundling constraint checks with main transaction logic atomically and with good UX.
• This method is backward-compatible and can be gas-efficient, making it practical for widespread adoption.
• Future EVM improvements for direct external state reads will make this approach even more powerful and affordable.
At Bitfinding, we are experts in low-level EVM execution for security and optimization. We are prototyping these state constraint principles with our simulation engine. We are actively working on moving the EVM ecosystem forward to prevent hacks through research and innovative services.
If you're interested in integrating this solution to protect yourself or your users, or want to discuss how to make your dApp interactions safer, we’d love your feedback or to explore collaboration.