xanoutas/solidity-security-findings huggingface dataset:
FaultDisputeGame's step preStateClaim calculation can be incorrect in some cases
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L269-L270


# Vulnerability details

## Impact
FaultDisputeGame's step preStateClaim calculation can be incorrect in some cases, causing `step()` to unexpectedly revert.

## Proof of Concept
In FaultDisputeGame::step, when the step is an attack, the preStateClaim might either be ABSOLUTE_PRESTATE(anchor state) or a prestate based on a claim in the game tree.

Because step is disputing claims at the execution trace leaf level (parentPos is at MAX_GAME_DEPTH), a prestate represents the VM state as a result of an instruction step. The entire execution trace's anchor state (ABSOLUTE_PRESTATE) should correspond to stepPos's indexAtDepth() at 0.

However, the current implementation will cause multiple stepPos's preStateClaim to use ABSOLUT_PRESTATE. When the step is an attack, whenever `(stepPos.indexAtDepth() % (1 << (MAX_GAME_DEPTH - SPLIT_DEPTH))) == 0` is true, preStateClaim will be the anchor state. 
```solidity
//packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol
  function step(
    uint256 _claimIndex,
    bool _isAttack,
    bytes calldata _stateData,
    bytes calldata _proof
  ) public virtual {
...
    // Determine the expected pre & post states of the step.
    Claim preStateClaim;
    ClaimData storage postState;
    if (_isAttack) {
...
|>      preStateClaim = (stepPos.indexAtDepth() %
        (1 << (MAX_GAME_DEPTH - SPLIT_DEPTH))) == 0
        ? ABSOLUTE_PRESTATE 
        : _findTraceAncestor(
          Position.wrap(parentPos.raw() - 1),
          parent.parentIndex,
          false
        ).claim;
...
        bool validStep = VM.step(_stateData, _proof, uuid.raw()) == postState.claim.raw();
...
```
(https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L269-L270)

Suppose SPLIT_DEPTH = 4, MAX_GAME_DEPTH = 8, step positions will be at depth 9.
`1 << (MAX_GAME_DEPTH - SPLIT_DEPTH)` = 16. Whenever stepPos.indexAtDepth() is 0, 16 ,32 ... 480, preStateClaim will be ABOSLUTE_PRESTATE.

(1) stepPos.indexAtDepth() = 0:
We can suppose attack claims are made all the way down the tree from depth 0 to depth 9 (step level). 

stePos's gIndex: 2^9 = 512
parent's gIndex: 256 (traceIndex: 0)

preStateClaim: ABSOLUTE_PRESTATE
postStateClaim: parent claim (traceIndex: 0)

vm.step() will compute output from ABOSULTE_PRESTATE to traceIndex 0.

(2) stepPos.indexAtDepth() = 16;
We can suppose attack claims are made all the way down from depth 0 to depth 5 (split level + 1), defend claim at depth 6, attack claims down to depth 9.

stePos's gIndex: 2^9+16 = 528
parent's gIndex: 264 (traceIndex: 8)

preStateClaim: ABSOLUTE_PRESTATE ( Note: this should be traceIndex 7 )
postStateClaim: parent claim (traceIndex: 8)

vm.step() will compute output from ABOSULTE_PRESTATE to traceIndex 8.

C
DoS of LPP challenges via `addLeaves()` allows griefing of honest challengers
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L584
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L619


# Vulnerability details

When users challenge an LPP in PreimageOracle, they are required to deliver proof that the pre/post states they are challenging belong to the LPP via Merkle inclusion proofs:

```Solidity
File: PreimageOracle.sol
566: 
567:     /// @notice Challenge a keccak256 block that was committed to in the merkle tree.
568:     function challengeLPP(
569:         address _claimant,
570:         uint256 _uuid,
571:         LibKeccak.StateMatrix memory _stateMatrix,
572:         Leaf calldata _preState,
573:         bytes32[] calldata _preStateProof,
574:         Leaf calldata _postState,
575:         bytes32[] calldata _postStateProof
576:     )
577:         external
578:     {
579:         // Verify that both leaves are present in the merkle tree.
580:         bytes32 root = getTreeRootLPP(_claimant, _uuid);
581:         if (
582:             !(
583:                 _verify(_preStateProof, root, _preState.index, _hashLeaf(_preState))
584:                     && _verify(_postStateProof, root, _postState.index, _hashLeaf(_postState))
585:             )
586:         ) revert InvalidProof();
---
604:         // Pay out the bond to the challenger.
605:         _payoutBond(_claimant, _uuid, msg.sender);
---
606:     }
---
607: 
608:     /// @notice Challenge the first keccak256 block that was absorbed.
609:     function challengeFirstLPP(
---
610:         address _claimant,
611:         uint256 _uuid,
612:         Leaf calldata _postState,
613:         bytes32[] calldata _postStateProof
614:     )
615:         external
616:     {
617:         // Verify that the leaf is present in the merkle tree.
618:         bytes32 root = getTreeRootLPP(_claimant, _uuid);
619:         if (!_verify(_postStateProof, root, _postState.index, _hashLeaf(_postState))) revert InvalidProof();
---
635:         // Pay out the bond to the challenger.
636:         _payoutBond(_claimant, _uuid, msg.sender);
---
637:     }
```

Because the Merkle proofs are checked against the current `root` of the tree, any change made to the tree will consequently change the `root` and invalidate the submitted proofs.

This creates the opportunity to frontrun a `challengeLPP` or `challengeFirstLPP` call with an `addLeavesLPP` call, that will effectively cause the challenges to fail.

While adding leaves (and therefore this DoS scenario) is only possible as long as the LPP is not finalized, it's important to note that the current LPP challenge economic model (the first to challenge gets the reward) incentivizes early challenges, so it's very reasonable, if not likely, that honest parties will actually challenge incorrect LPPs before their finalization.

Malicio
The clock extension is not applied correctly at `SPLIT_DEPTH - 1`
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/main/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L371-L382


# Vulnerability details

The `CLOCK_EXTENSION` is a feature meant for honest challengers to counter a freeloader claim, a freeloader claim is described as follows:

Consider an attacker claim denoted by A in the subtree below. Suppose that this claim is valid, then the correct move for the honest party is to defend the claim. The honest party's claim is denoted by claim H.
```
   A  
 /   \
F     H
```
To prevent themselves from losing the bond, suppose the attacker then attacks claim A with the claim F. Due to the leftmost priority, they would receive the bond if their freeloader claim F remains uncountered. This is possible if the attacker's chess clock has very little time left because the honest party needs to use the attacker's chess clock to counter the freeloader claim. 

To resolve this, Optimism uses clock extensions, where if a chess clock has less than `CLOCK_EXTENSION` seconds left, it is extended to `CLOCK_EXTENSION` seconds.

If we observe how the `CLOCK_EXTENSION` is applied, notice that if the attacker makes claim A at `SPLIT_DEPTH - 1`, the attacker's chess clock will receive `CLOCK_EXTENSION * 2` seconds. This is so that the honest party has more time to counter the freeloader claim F as they need to generate the correct execution trace bisection root by executing the offchain FPVM.

```solidity
File: FaultDisputeGame.sol
371:     // If the remaining clock time has less than `CLOCK_EXTENSION` seconds remaining, grant the potential
372:     // grandchild's clock `CLOCK_EXTENSION` seconds. This is to ensure that, even if a player has to inherit another
373:     // team's clock to counter a freeloader claim, they will always have enough time to to respond. This extension
374:     // is bounded by the depth of the tree. If the potential grandchild is an execution trace bisection root, the
375:     // clock extension is doubled. This is to allow for extra time for the off-chain challenge agent to generate
376:     // the initial instruction trace on the native FPVM.
377:     if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - CLOCK_EXTENSION.raw()) {
378:         // If the potential grandchild is an execution trace bisection root, double the clock extension.
379:         uint64 extensionPeriod =
380:             nextPositionDepth == SPLIT_DEPTH - 1 ? CLOCK_EXTENSION.raw() * 2 : CLOCK_EXTENSION.raw();
381:         nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - extensionPeriod);
382:     }
```

However, notice that in the code above, if the attacker manipulates their clock to a value between `CLOCK_EXTENSION` and `CLOCK_EXTENSION * 2`, the clock extension will not be granted.

We can deduce from the decision to grant an extension of `CLOCK_EXTENSION * 2` seconds at `SPLIT_DEPTH - 1` that `CLOCK_EXTENSION` seconds is not enough time the ensure the honest challenger can generate the ex
During the staticcall to the precompile the revert() couldn't be catched, which could lead to unintended behaviour.
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L223-L233
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L286-L299
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L354-L362
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/PreimageOracle.sol#L284-L294


# Vulnerability details

## Impact
The preimageOracle calls precompile addresses with the purpose to:

1. Verify the KZG proof by calling the point evaluation precompile
2. compute SHA2-256 hash 
3. Or just call arbitrary precompile to get the result.
---
However, the problem is that if the staticcall to the precompiled contract fails(proof is invalid / sha256 calculation failed) the function will not be able to revert, since the call would always succeed, and it would simply move forward, which could lead to unintended behaviour.
- Why the function wouldn't be able to catch the revert? Because When calling precompiles, be aware that on error/”failure”, the call is still successful. A failed precompile call simply has a returndatasize of 0. It means that, when calling precompiles, do the check on the returndatasize not the "success" of the call to determine if it failed

If we dive deeper into the problem, we could find that it doesn't return 0, it returns nothing! So, instead of checking for 0, we would need to check the return data size because such behaviour could lead to the unintended behaviour. the staticcall(s) will fail, thus returning no result and the execution will continue with the incorrect data.

Additionaly, it worth to mention that during the loadPrecompilePreimagePart function, the precompile address isn't checked. It means, if the address will be passed without code deployed, the static call will always return true! -> https://dacian.me/solidity-inline-assembly-vulnerabilities#heading-external-call-to-non-existent-contract
It could lead to the unintended behaviour and potential manipulations of the preimages

## Proof of Concept
Traces:
  [3350501] PreimageOracle_Test::setUp()
    ├─ [3305933] → new PreimageOracle@0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f
    │   └─ ← [Return] 14793 bytes of code
    ├─ [0] VM::label(PreimageOracle: [0x5615dEB798BB3E4dFa0139dFa1b3D433Cc23b72f], "PreimageOracle")
    │   └─ ← [Return] 
    └─ ← [Stop] 

  [1040497855] PreimageOracle_Test::test_loadPrecompilePreimagePart_succeeds()
    ├─ [0] VM::expectRevert(InvalidProof())
    │   └─ ← [Return] 
    ├─ [1040488837] PreimageOracle::loadPrecompilePreimagePart(0, 0x000000000000000000000000000000000000000A, 0xdeadbeef)
    │   ├─ [0] console::log("Gas Left before starting the assembly block", 105693433
Honest party's move could become invalid when re-org takes place
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/DisputeGameFactory.sol#L116
https://github.com/Vectorized/solady/blob/a95f6714868cfe5d590145f936d0661bddff40d2/src/utils/LibClone.sol#L458
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L319


# Vulnerability details

## Impact
When block re-org takes place, honest party's move could become invalid. A similar issue has been [raised](https://github.com/sherlock-audit/2024-02-optimism-2024-judging/issues/201) earlier, and this report shows two new scnearios, which the [fix](https://github.com/ethereum-optimism/optimism/pull/10520/files#diff-d25b365609feaebe61ed8cf3547bd8f7fbe26aa6a5a30aab21788e9fad7a7c6a) fails to address.

## Proof of Concept
A new parameter _claim has been added to ensure the target is the expected one, but this is still not enough.
```
function move(Claim _disputed, uint256 _challengeIndex, Claim _claim, bool _isAttack) public payable virtual {
```
The factory uses create instead of create2 to deploy the proxy, consider the following scenario:
1. Evil Alice creates a invalid root claim A and game P.
2. Seemingly honest Bob attacks A with claim B in game P.
3. Seemingly honest Bob creates a valid root claim C and game Q.
4. Evil Alice attacks C with claim B(valid, same hash as step 2) in game Q.
5. Honest Coco defends B with claim D in game Q.

Alice and Bob are friends actually and they use another smart contract to interact with the factory and game. At the end of step 1 and 3, A,P and C,Q are stored in the contract, and in step 2 and 4, the contract executes `P.move(A,...)`and `Q.move(C,...)`. Coco simply uses an eoa at step 5.

Then reorg happens, transaction order becomes 34125. The address of game P and Q are swapped (since the deployed address is derived from sender and nonce). Now it will look like
1. Seemingly honest Bob creates a valid root claim C and game Q.
2. Evil Alice attacks C with claim B(valid, same hash with step 4) in game Q.
3. Evil Alice creates a invalid root claim A and game P.
4. Seemingly honest Bob attacks A with claim B in game P.
5. Honest Coco defends B with claim D in game ~~Q~~P.

Alice and Bob are still in their desired game but Coco will be in a wrong game. As a result, her bond will be lost.

```
proxy_ = IDisputeGame(address(impl).clone(abi.encodePacked(msg.sender, _rootClaim, parentHash, _extraData)));
......
instance := create(value, add(m, add(0x0b, lt(n, 0xffd3))), add(n, 0x37))
```
Suppose create2 is used to address the previous scenario, but within the same game, reorg could still impact the honest party. Consider the following scenario.
1. Evil Alice creates an invalid root claim A.
2. Honest Coco attacks A with claim B.
3. Evil Alice attacks A with an invalid claim X.
4. Evil Alice attacks B with the valid claim C.
5. Honest Coco defen
Extra time allocated for generating execution trace can be skipped
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L371-L382


# Vulnerability details


## Impact

In the `FaultDisputeGame` contract, the `CLOCK_EXTENSION` mechanism is implemented as follows:


```solidity
if (nextDuration.raw() > MAX_CLOCK_DURATION.raw() - CLOCK_EXTENSION.raw()) {
    // If the potential grandchild is an execution trace bisection root, double the clock extension.
    uint64 extensionPeriod =
        nextPositionDepth == SPLIT_DEPTH - 1 ? CLOCK_EXTENSION.raw() * 2 : CLOCK_EXTENSION.raw();
    nextDuration = Duration.wrap(MAX_CLOCK_DURATION.raw() - extensionPeriod);
}
```

As described in the comments and OP docs, the `CLOCK_EXTENSION` mechanism ensures that when disputing "freeloader" claims, the challenger always has at least `CLOCK_EXTENSION` seconds to respond to follow-up claims. This is crucial because the challenger inherits their adversary's clock when disputing "freeloader" claims.

Also, in the above code, there is a doubling of the `CLOCK_EXTENSION` if the potential grandchild is an execution trace root. This extra time is granted because the game would enter a different phase that requires the slower trace computation be performed.

However, there is a problem with this mechanism. If there are between `CLOCK_EXTENSION + 1` and `2 * CLOCK_EXTENSION - 1` seconds left, the challenger is not given extra time and therefore would have less than `2 * CLOCK_EXTENSION` seconds to respond. This behavior implies two things:

1. A user can be required to generate a trace in response to a claim in less than `2 * CLOCK_EXTENSION` seconds, which may be unexpected and unreasonable. This can happen by accident, or on purpose in the case of "freeloader claims" where the honest user inherits the adversary's clock.

2. A user can receive *more* time on their clock by waiting longer to respond. This makes the game less efficient.


## Proof of Concept

The following test can be added to `packages/contracts-bedrock/test/dispute/FaultDisputeGame.t.sol`:


```solidity
function test_double_clock_extension_bug() public {
    /**********************************************************************
    1. General setup
    **********************************************************************/
    Clock clock;
    Claim disputed;
    uint256 bond;

    Claim claim = _dummyClaim();
    uint256 splitDepth = gameProxy.splitDepth();
    uint64 maxClockDuration = gameProxy.maxClockDuration().raw();
    uint64 clockExtension = gameProxy.clockExtension().raw();

    // Bisect all the way down to the move above the `SPLIT_DEPTH`.
    for (uint256 i = 0; i < splitDepth - 2; i++) {
        bond = _getRequiredBond(i);
        (,,,, disputed,,) = gameProxy.claimData(i);
        gameProxy.attack{ value: bond }(disputed, i, claim);
    }

    bond = _getRequiredBond(splitDepth - 2);
    (,,,, disputed,,) = gameProxy.claimData(splitDepth - 2)
The fault dispute game can encounter unexpected situations
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/dispute/FaultDisputeGame.sol#L453


# Vulnerability details

## Impact
A `fault dispute game` is initialized with an `output root` corresponding to the state of `L2` at a specific `L2 block number`. 
`Execution trace subgames` at `SPLIT_DEPTH + 1` are initialized with a `claim` that commits to the entire `execution trace` between two consecutive `output roots` (a `block n` -> `n+1` state transition).
However, in some cases, the `claim` at `SPLIT_DEPTH + 1` can incorrectly commit to the `execution trace` between the same `block`, i.e., `block n` => `n` state transition. 
This is a really weird situation. 
Additionally, an incorrect disputed `L2 block number` can be saved to the `preimage oracle`.

## Proof of Concept
Let me give an example:
The `SPLIT_DEPTH` is `4`, and the `startingOutputRoot` is `{ l2BlockNumber: 5, root: root }`.
A `bad proposer` submits an invalid `claim` `C` for `block 14`.
The `nodes` at `SPLIT_DEPTH` are `16`, `17`, ..., `31`, and these `nodes` represent individual `blocks`.
`Node 16` represents `block 6`, `node 17` represents `block 7`, and `nodes 24` to `31` represent `block 14`.
An honest `challenger` detects that the `claim` for `block 14` is incorrect and responds with a correct `claim` `D` for `block 13`, moving to `node 2` (`trace index` `7` for `block 13`). 
The `proposer` then `defends` with a correct `claim` `E` for `block 14` and moves to `node 6` (`(2 + 1) * 2`).
If the `challenger` does not make any further `moves`, the invalid `claim` will win. 
Therefore, the `challenger` must `defend` `node 6`, as they can't `attack` it since it's a correct `claim` for `block 14`.
The game then moves to `node 14` (`(6 + 1) * 2`), and the `claim` at this `node` is also the correct `claim` `E` for `block 14`. 
The `proposer` `attacks` `node 14` with the original incorrect `claim` `C` for `block 14` and moves to `node 28` (`14 * 2`).
This means that both players agree on `node 27` but not on `node 28`, even though both `nodes` represent the same `block 14`. 
Additionally, in the `addLocalData` function, the `disputed L2 block number` is incorrectly calculated as `18` instead of `14`. 
```
function addLocalData(uint256 _ident, uint256 _execLeafIdx, uint256 _partOffset) external {
    if (status != GameStatus.IN_PROGRESS) revert GameNotInProgress();
    (Claim starting, Position startingPos, Claim disputed, Position disputedPos) =
        _findStartingAndDisputedOutputs(_execLeafIdx);
    Hash uuid = _computeLocalContext(starting, startingPos, disputed, disputedPos);
    IPreimageOracle oracle = VM.oracle();
    if (_ident == LocalPreimageKey.L1_HEAD_HASH) {
    } else if (_ident == LocalPreimageKey.STARTING_OUTPUT_ROOT) {
    } else if (_ident == LocalPreimageKey.DISPUTED_OUTPUT_ROOT) {
    } else if (_ident == LocalPreimageKey.DISPUTED_L2_BLOCK_NUMBER) {
        uint256 l2Number = starting
The `MIPS` doesn't implement `ADD`, `ADDI`, and `SUB` instructions correctly
Audit: 2024-07-optimism-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/MIPS.sol#L921
https://github.com/code-423n4/2024-07-optimism/blob/70556044e5e080930f686c4e5acde420104bb2c4/packages/contracts-bedrock/src/cannon/MIPS.sol#L929


# Vulnerability details

According to the specification (https://www.cs.cmu.edu/afs/cs/academic/class/15740-f97/public/doc/mips-isa.pdf), `ADD` (page A-28), `ADDI` (page A-29), and `SUB` (page A-144) instructions should raise an Integer Overflow exception if overflow occurs. The current implementation simply wraps the result in such cases and does not raise any exceptions.
```solidity
...
function execute(uint32 insn, uint32 rs, uint32 rt, uint32 mem) internal pure returns (uint32 out) {
    unchecked {
        ...
        else if (func == 0x20) {
            return (rs + rt);
        }
        ...
        else if (func == 0x22) {
            return (rs - rt);
        }
        ...
    }
}
...
```
This inconsistency leads to a situation where `MIPS` contract can't correctly emulate such cases and therefore allows malicious actors to successfully forge invalid claims and challenge valid claims.

## Impact
An inconsistent implementation of big-endian 32-bit MIPS32 architecture in the `MIPS` contract allows malicious actors to successfully forge invalid claims and challenge valid claims.

## Proof of Concept
\-

## Tools Used
Manual Review

## Recommended Mitigation Steps
Consider raising Integer Overflow exception for `ADD`, `ADDI`, and `SUB` instructions if overflow occurs.


## Assessed type

Math