ABSTRACT
Dedaub was commissioned to perform a security audit of the system contracts for EIP2935. In total only one advisory issue was found, related to an inconsistency between the buffer size being used in the contract and the buffer size defined in the EIP.
BACKGROUND
Historically, Ethereum’s execution model assumed that clients store and serve recent block hashes from local node history. However, as the ecosystem moves toward “stateless clients,” which do not maintain full local state, this assumption has become problematic.
Stateless clients need a way to verify block hashes without depending on a fully synchronized node. One solution is to include the necessary historical block hashes directly in the state accessible by the EVM, allowing them to be easily retrieved and verified against cryptographic proofs like Merkle or Verkle trees.
EIP-2935 proposes storing and serving historical block hashes from a specially designated system contract. By writing the last 8192 block hashes into a ring buffer inside the state, the EVM itself can then query these hashes directly. This approach does not alter the existing BLOCKHASH
opcode semantics, but rather provides an additional way to access older hashes through contract calls.
SETTING & CAVEATS
This audit report mainly covers the contracts of the sys-asm repository at commit 1d679164e03d73dc7f9a5331b67fd51e7032b104
, as well as the code, pseudocode, and specifications in the EIP (considering the EIP definition at commit 28ee5e8562a1d4437032718dab1b8fa5b031bda4 of the ethereum/EIPs repository).
2 auditors worked on the codebase for 2 days (each) on the following contracts:
src/
execution_hash//
├── ctor.eas
└── main.eas
Throughout the audit the auditors reviewed the code at numerous levels of abstraction, including the Geas code itself, the disassembled bytecode, as well as the three-address-code (TAC) and decompilation produced by our decompiler. This was done to ensure both human and programmatic understanding of the code, as well as isolating any potential bugs introduced by Geas.
The audit was primarily focused on the security of the smart contracts, and their adherence to the given specification. How the rest of the EIP is implemented into the protocol is outside of the scope of this audit, however the auditors did attempt to identify potential cross cutting issues based on the code in scope.
CONTRACT LOGIC OVERVIEW
The audited smart contract includes 2 execution flows: set()
which can only be called during the block’s processing using the special system address (0xfffffffffffffffffffffffffffffffffffffffe
) and stores the block hash of the previous block (block.number - 1), and get()
which is callable by all other addresses and returns the block hash of a block, given its block number with block.number - 8191 <= number <= block.number - 1.
As the smart contract does not adhere to the ABI specification, the called method is chosen based on the contract’s caller.
To bind the amount of storage used by the contract, block hashes are stored in a ring buffer of 8191 slots. Given a valid, recent block number, its block hash is stored in the storage slot at index number % 8191.
The annotated decompiled code expresses the entirety of the contract’s logic:
contract EIP2935{
function __function_selector__(
uint256 calldata_0_32
) public payable {
if (0xfffffffffffffffffffffffffffffffffffffffe == msg.sender) {
// set()
STORAGE[(block.number - 1) % 8191] = bytes32(calldata_0_32);
exit;
} else {
// get()
require(!(msg.data.length - 32));
require(calldata_0_32 <= block.number - 1);
require(block.number - calldata_0_32 <= 8191);
return STORAGE[calldata_0_32 % 8191];
}
}
}
The following diagram annotates the control flow of the contract, showing the stack values at each point.
VULNERABILITIES & FUNCTIONAL ISSUES
This section details issues affecting the functionality of the contract. Dedaub generally categorizes issues according to the following severities, but may also take other considerations into account such as impact or difficulty in exploitation:
Issue resolution includes “dismissed” or “acknowledged” but no action taken, by the client, or “resolved”, per the auditors.
CRITICAL SEVERITY
[No critical severity issues]
HIGH SEVERITY
[No high severity issues]
MEDIUM SEVERITY
[No medium severity issues]
LOW SEVERITY
[No low severity issues]
OTHER / ADVISORY ISSUES
This section details issues that are not thought to directly affect the functionality of the project, but we recommend considering them.
Inconsistent History Buffer Size
Advisory | Status: RESOLVED
The contract execution_hash/main.eas
defines the history window to be 8191, whilst the EIP defines HISTORY_SERVE_WINDOW
to be 8192. Looking at another system contract EIP 4788, this uses a buffer size of 8191.
DISCLAIMER
The audited contracts have been analyzed using automated techniques and extensive human inspection in accordance with state-of-the-art practices as of the date of this report. The audit makes no statements or warranties on the security of the code. On its own, it cannot be considered a sufficient assessment of the correctness of the contract. While we have conducted an analysis to the best of our ability, it is our recommendation for high-value contracts to commission several independent audits, a public bug bounty program, as well as continuous security auditing and monitoring through Dedaub Security Suite.
ABOUT DEDAUB
Dedaub offers significant security expertise combined with cutting-edge program analysis technology to secure some of the most prominent protocols in DeFi. The founders, as well as many of Dedaub’s auditors, have a strong academic research background together with a real-world hacker mentality to secure code. Protocol blockchain developers hire us for our foundational analysis tools and deep expertise in program analysis, reverse engineering, DeFi exploits, cryptography and financial mathematics.