xanoutas/solidity-security-findings huggingface dataset:
Undesirable behaviour in the event of a fork immediately after BOLD upgrade
Audit: 2024-05-arbitrum-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/rollup/RollupUserLogic.sol#L62


# Vulnerability details

In Contract RollupUserLogic, in the function removeWhitelistAfterValidatorAfk() there is a check for Number of blocks since the last confirmed assertion before the validator whitelist is removed, which is configured as 28 days.  The validatorWhitelistDisabled can be set to True only after this period of 28 days has passed (after which the validators are considered afk).

However the same check is missing in the removeWhitelistAfterFork(). Here the only check is if the chainid is different than the deployTimeChainId.
If there is a fork just after the BOLD upgrade and before the 28 days, then anyone can trigger this function to set the validatorWhitelistDisabled to True and opening up the protocol to permissionless Validators.

This is not desirable and will have undesired behaviour to the protocol Assertion/Validation logic.

### Proof of Concept
Contract : RollupUserLogic.sol 
Function : removeWhitelistAfterFork()

### Recommended Mitigation Steps
Add similar check to the function removeWhitelistAfterFork().

```
    function removeWhitelistAfterFork() external {
        require(!validatorWhitelistDisabled, "WHITELIST_DISABLED");
        require(_chainIdChanged(), "CHAIN_ID_NOT_CHANGED");
++      require(_validatorIsAfk(), "VALIDATOR_NOT_AFK");
        validatorWhitelistDisabled = true;
    }
```


## Assessed type

Invalid Validation
Incorrect check in `requireInactiveStaker` allows validators to withdraw their stakes for pending assertions
Audit: 2024-05-arbitrum-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/rollup/RollupCore.sol#L573


# Vulnerability details

## Impact
Validators can withdraw their assets during pending assertions

## Proof of Concept
In BoLD rollup, the validators are allowed to withdraw their assets when they are inactive which means when they are not a part of current assertions, and this is checked in `requireInactiveStaker` function as follows:
```solidity
function requireInactiveStaker(address stakerAddress) internal view {
    require(isStaked(stakerAddress), "NOT_STAKED");
    // A staker is inactive if
    // a) their last staked assertion is the latest confirmed assertion
    // b) their last staked assertion have a child
    bytes32 lastestAssertion = latestStakedAssertion(stakerAddress);
    bool isLatestConfirmed = lastestAssertion == latestConfirmed();
    bool haveChild = getAssertionStorage(lastestAssertion).firstChildBlock > 0;
    require(isLatestConfirmed || haveChild, "STAKE_ACTIVE");
}
```
It assumes that a staker is inactive when its latest assertion has a child, which is wrong.
Here's an example diagram that shows the case:
![Withdraw logic](https://i.ibb.co/Mn0h8jp/withdraw.png)

As shown in the diagram, the validator of `Assertion1` can withdraw their stake even though the assertion is in pending.

Here's a test case to show withdrawal is possible when the assertion is in the process(Can be added in `Rollup.t.sol` to test):
```solidity
function _createNewState() internal returns (AssertionState memory afterState, bytes32 inboxAcc) {
    uint256 sequencerInboxCount = userRollup.bridge().sequencerMessageCount();

    afterState.machineStatus = MachineStatus.FINISHED;
    afterState.globalState.bytes32Vals[0] = keccak256(abi.encodePacked("BLOCK_HASH", uint256(sequencerInboxCount))); // blockhash
    afterState.globalState.bytes32Vals[1] = keccak256(abi.encodePacked("SEND_ROOT", uint256(sequencerInboxCount))); // sendroot
    afterState.globalState.u64Vals[0] = uint64(sequencerInboxCount); // inbox count
    afterState.globalState.u64Vals[1] = 0; // pos in msg

    inboxAcc = userRollup.bridge().sequencerInboxAccs(sequencerInboxCount - 1);

    _createNewBatch();
} 

function testAuditWithdrawDuringAssertion() public {
    AssertionState memory s0;
    s0.machineStatus = MachineStatus.FINISHED;

    (AssertionState memory s1, bytes32 acc1) = _createNewState();

    bytes32 a1Hash = RollupLib.assertionHash({
        parentAssertionHash: genesisHash,
        afterState: s1,
        inboxAcc: acc1
    });

    AssertionInputs memory assertion = AssertionInputs({
        beforeStateData: BeforeStateData({
            sequencerBatchAcc: bytes32(0),
            prevPrevAssertionHash: bytes32(0),
            configData: ConfigData({
                wasmModuleRoot: WASM_MODULE_ROOT,
                requiredStake: BASE_STAKE,
                challengeManager: address(challengeManager),
Wrong usage of origin checking in sequence inbox
Audit: 2024-05-arbitrum-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/bridge/SequencerInbox.sol#L577
https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/bridge/SequencerInbox.sol#L601


# Vulnerability details

## Impact
Batch posters will not get their gas fees reimbursed.

## Proof of Concept
Using Arbitrum's `SequencerInbox` contract, sequencers(or aka batch posters) submit tx batches through some methods like `addSequencerL2Batch` or `addSequencerL2BatchDelayProof`.
The sequencers get their gas fee reimbursed based on the length of the data they post, as the following code snippet shows. [SequencerInbox.sol#L818-L821](https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/bridge/SequencerInbox.sol#L818-L821)
```solidity
if (calldataLengthPosted > 0 && !isUsingFeeToken) {
    // only report batch poster spendings if chain is using ETH as native currency
    submitBatchSpendingReport(dataHash, seqMessageIndex, block.basefee, 0);
}
```

However, in `addSequencerL2Batch` and `addSequencerL2BatchDelayProof` functions, they pass `false` as `isFromOrigin` flag, regardless of `msg.sender`.
In fact, these functions are called by either whitelisted EOA batch posters or the rollup contract, which means when these functions are called by whitelisted EOA batch posters, they will not be able to get their gas fees reimbursed.

## Tools Used
Manual Review

## Recommended Mitigation Steps
It should check if msg.sender is an EOA and pass true if that's the case.

```diff
addSequencerL2BatchFromCalldataImpl(
    sequenceNumber,
    data,
    afterDelayedMessagesRead,
    prevMessageCount,
    newMessageCount,
-   false
+   msg.sender == tx.origin
);
```


## Assessed type

Context
Adversary can force honest party to lose stake to challenge their incorrect edges
Audit: 2024-05-arbitrum-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/6f861c85b281a29f04daacfe17a2099d7dad5f8f/src/challengeV2/libraries/EdgeChallengeManagerLib.sol#L608-L610


# Vulnerability details

## Impact
Once an correct edge is confirmed, it can no longer be bisected. If its rivals reaches the same position, honest party must create another invalid edge and lose the stake in order to challenge that rival. Otherwise incorrect edges could be confirmed.

## Proof of Concept
According to the contest doc
>Attack ideas (where to focus for bugs)
Assuming there is at least one honest participant, no incorrect assertions or edges can be confirmed.

Suppose a correct assertion and an incorrect assertion are having a battle. They now disagree on a length one edge of level 4. Then, honest party creates an layer 0 edge Y in level 5. After some time, Y accumulates enough unrivaled time and gets confirmed. Then the adversary creates another layer 0 edge N at the same position, and bisected it. Since the Y is already confirmed, it can no longer be bisected.

    function bisectEdge(EdgeStore storage store, bytes32 edgeId, bytes32 bisectionHistoryRoot, bytes memory prefixProof)
        internal
        returns (bytes32, EdgeAddedData memory, EdgeAddedData memory)
    {
        if (store.edges[edgeId].status != EdgeStatus.Pending) {
            revert EdgeNotPending(edgeId, store.edges[edgeId].status);
        }

If the honest party wishes to rival N, they will need to stake on another invalid edge at level 5 layer 0, which can be continuously bisected. But by doing so, honest party will lose the stake.

Otherwise, although F cannot be confirmed since T is already confirmed,
1) the ancestors of F, and the corresponding assertion, can accumulate unrivaled time, which may help the adversary win the challenge game.
2) the descendants of F cannot be rivaled either, and they will eventually be confirmed, which breaks the invariant *no incorrect assertions or edges can be confirmed*.

## Tools Used
Manual

## Recommended Mitigation Steps
Allow confirmed edges to be bisected.





## Assessed type

Context
`BOLDUpgradeAction.sol` will fail to upgrade contracts due to error in the `perform` function
Audit: 2024-05-arbitrum-foundation-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2024-05-arbitrum-foundation/blob/main/src/rollup/BOLDUpgradeAction.sol#L350-L359


# Vulnerability details

## Impact
An error in the `BOLDUpgradeAction.sol` contract prevents it from upgrading and deploying new BOLD contracts.

## Proof of Concept
The `perform` function serves as the entry point in the `BOLDUpgradeAction.sol` and is responsible for migrating stakers from the old rollup and deploying the challenge manager with a new rollup contract.

One of the first subroutines in this function is the `cleanupOldRollup()`.

```solidity
    function perform(address[] memory validators) external {
        // tidy up the old rollup - pause it and refund stakes
        cleanupOldRollup();
```
This subroutine pauses the old rollup contract and attempts to refund existing stakers.

```solidity
    function cleanupOldRollup() private {
        IOldRollupAdmin(address(OLD_ROLLUP)).pause();

>>      uint64 stakerCount = ROLLUP_READER.stakerCount();
        // since we for-loop these stakers we set an arbitrary limit - we dont
        // expect any instances to have close to this number of stakers
        if (stakerCount > 50) {
            stakerCount = 50;
        }
        for (uint64 i = 0; i < stakerCount; i++) {
>>          address stakerAddr = ROLLUP_READER.getStakerAddress(i);
            OldStaker memory staker = ROLLUP_READER.getStaker(stakerAddr);
            if (staker.isStaked && staker.currentChallenge == 0) {
                address[] memory stakersToRefund = new address[](1);
                stakersToRefund[0] = stakerAddr;

                IOldRollupAdmin(address(OLD_ROLLUP)).forceRefundStaker(stakersToRefund);
            }
        }

        // upgrade the rollup to one that allows validators to withdraw even whilst paused
        DoubleLogicUUPSUpgradeable(address(OLD_ROLLUP)).upgradeSecondaryTo(IMPL_PATCHED_OLD_ROLLUP_USER);
    }
```
This function contains a bug that prevents execution of the subsequent procedures. Let's check the `forceRefundStaker` in the old rollup contract.

According to:
https://docs.arbitrum.io/build-decentralized-apps/reference/useful-addresses 

Proxy: https://etherscan.io/address/0x5eF0D09d1E6204141B4d37530808eD19f60FBa35
Implementation: https://etherscan.io/address/0x72f193d0f305f532c87a4b9d0a2f407a3f4f585f#code

RollupAdminLogic.sol
```solidity
    function forceRefundStaker(address[] calldata staker) external override whenPaused {
        require(staker.length > 0, "EMPTY_ARRAY");
        for (uint256 i = 0; i < staker.length; i++) {
            require(_stakerMap[staker[i]].currentChallenge == NO_CHAL_INDEX, "STAKER_IN_CHALL");
            reduceStakeTo(staker[i], 0);
>>          turnIntoZombie(staker[i]);
        }
        emit OwnerFunctionCalled(22);
    }
```

RollupCore.sol
```solidity
    function turnIntoZombie(address stakerAddress) internal {
        Staker storage staker = _stakerMap[stakerAddress];
        _zombies.push(Zombie(stakerAddress, stake
Duplicated / Split H -> H from 625 [1666621826143]
Audit: 2022-07-golom-findings
Severity: HIGH

Judge has assessed an item in Issue #625 as High risk. The relevant finding follows:

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L78-L82
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L198-L206
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L213
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L101-L105


# Vulnerability details

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L78-L82
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L198-L206
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/vote-escrow/VoteEscrowDelegation.sol#L213

# `storage` used in `VoteEscrow`, modifying data it shouldn't, and vice versa

In both the functions `delegate` and `removeDelegation` (and `removeElement`), a storage var is used, causing to modify the old checkpoint too.

In `_writeCheckpoint`, it's the other way around - `memory` is used in case that `nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number` is true and therefore the modification which should be saved to storage - isn't saved:

```solidity
        Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];

        if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) {
            oldCheckpoint.delegatedTokenIds = _delegatedTokenIds;
        }
```

## Impact
* In case of `delegate` it will add delegation to previous checkpoint, giving the delegatee *more* voting power than it should have in previous blocks.
* In case of `removeDelegation` it will remove delegation from previous checkpoint, giving the delegatee *less* voting power that he should have.

The bug in `_writeCheckpoint` will only 'work' once the other bugs are fixed, but once that's done the impact can be high:

* This will fail to remove or add delegation, in case the delegatee's checkpoint was already modified this block.
* An attacker can use this to multiplying his delegation power endlessly, by adding a delegation and removing it in the same block (using a contract to run those 2 functions in the same tx). The delegation will succeed but the removal will fail, this way each time this runs the user delegates again the same token.
* Once the attacker uses this to gain the majority of voting power, he can propose a vote to transfer ownership of contracts to himself, and once that is done he can mint as much tokens as he'd like to himself etc. (I've elaborated more on this
Upgraded Q -> H from 744 [1666621202565]
Audit: 2022-07-golom-findings
Severity: HIGH

Judge has assessed an item in Issue #744 as High risk. The relevant finding follows:

1. _writeCheckpoint will fail to insert an initial checkpoint
https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L101

Due to solidity 0.8 overflow/underflow protection, accessing checkpoints[toTokenId][nCheckpoints - 1] will throw if nCheckpoints == 0.

As it is not possible to insert the initial checkpoint, _writeCheckpoint will always fail. This issue currently prevents adding a delegation.
Duplicated / Split H -> H from 461 [1666620942709]
Audit: 2022-07-golom-findings
Severity: HIGH

Judge has assessed an item in Issue #461 as High risk. The relevant finding follows:

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L109


# Vulnerability details

## Impact
In `VoteEscrowDelegation` contract, the [_writeCheckpoint](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L94-L109) internal function has major logic errors which will make the [delegate](https://github.com/code-423n4/2022-07-golom/blob/7bbb55fca61e6bae29e57133c1e45806cbb17aa4/contracts/vote-escrow/VoteEscrowDelegation.sol#L71-L89) external function to always revert. 

## Proof of Concept

Suppose an user calls the delegate function for the first time for a particular `toTokenId`. Since its the first time, the `nCheckpoints` for that token will be zero. Which means the `else` part of the condition will be executed. Here, we call the internal function `_writeCheckpoint` with a value of `0` for `nCheckpoints` parameter. Pasted below is the delegate function code with some extra comments:
```solidity
    function delegate(uint256 tokenId, uint256 toTokenId) external {  
        require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
        require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');

        delegates[tokenId] = toTokenId;
--->>>  uint256 nCheckpoints = numCheckpoints[toTokenId];   //this will be zero for the first time.

        if (nCheckpoints > 0) {
            Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1];
            checkpoint.delegatedTokenIds.push(tokenId);
            _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds);
--->>>  } else {      // so control goes here.
            uint256[] memory array = new uint256[](1);
            array[0] = tokenId;
--->>>      _writeCheckpoint(toTokenId, nCheckpoints, array);  //internal function called with 0 value for nCheckpoints parameter
        }

        emit DelegateChanged(tokenId, toTokenId, msg.sender);
    }
```

Now looking at the `_writeCheckpoint` internal function:

```solidity
    function _writeCheckpoint(
        uint256 toTokenId,
        uint256 nCheckpoints,
        uint256[] memory _delegatedTokenIds
    ) internal {
        require(_delegatedTokenIds.length < 500, 'VVDelegation: Cannot stake more');
        //see the line below. "nCheckpoints - 1" is used as an index. Which results in underflow panic error.
-->>>   Checkpoint memory oldCheckpoint = checkpoints[toTokenId][nCheckpoints - 1];

        if (nCheckpoints > 0 && oldCheckpoint.fromBlock == block.number) {
            oldCheckpoint.delegatedTokenIds = _delegatedTokenIds;
        } else {
            checkpoints[toTokenId][nCheckpoints] = Checkpoint(block.number, _delegatedTokenIds
`delegate` modifies old checkpoint
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/main/contracts/vote-escrow/VoteEscrowDelegation.sol#L79-L80


# Vulnerability details

## Impact
VoteEscrow tokens are used as voting tokens for a GovernorAlpha governance. It shouldn't be possible to modify an old (meaning for blocks older than block.number) checkpoint, otherwise it's possible to buy tokens just to vote for a proposal and then sell them.

In `delegate` function however there's such a bug.
```js
    function delegate(uint256 tokenId, uint256 toTokenId) external {
        require(ownerOf(tokenId) == msg.sender, 'VEDelegation: Not allowed');
        require(this.balanceOfNFT(tokenId) >= MIN_VOTING_POWER_REQUIRED, 'VEDelegation: Need more voting power');

        delegates[tokenId] = toTokenId;
        uint256 nCheckpoints = numCheckpoints[toTokenId];

        if (nCheckpoints > 0) {
            Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1];
-->         checkpoint.delegatedTokenIds.push(tokenId);
            _writeCheckpoint(toTokenId, nCheckpoints, checkpoint.delegatedTokenIds);
        } 
        ...
    }
```
`_writeCheckpoint` correctly makes a new checkpoint for the correct block number, but before that the code adds a new token to the last `checkpoint`, which has an older block number.

## Proof of Concept
A new governance proposal started. Alice has already a token `id1`. She can buy (and later sell) a token `id2`.  Calling `delegate(id2, id1)` she adds `id2` to her delegations, and she's able to vote also using its balance.

Let's say she also has a token `id3` before the proposal started. She can call `delegate(id2, id3)` (deleting `id2` delegates beforehand) and `id2` will also count when she votes using `id3` (it's basically a double count for `id2`). The process can of course be repeated with multiple tokens, leading to abusers getting way more voting powers than normal.

## Recommended Mitigation Steps
`Checkpoint storage checkpoint = checkpoints[toTokenId][nCheckpoints - 1];` to `memory`.
Miscalculated payout to NFT sellers completing ERC1155 bids
Audit: 2022-07-golom-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L381
https://github.com/code-423n4/2022-07-golom/blob/e5efa8f9d6dda92a90b8b2c4902320acf0c26816/contracts/core/GolomTrader.sol#L389-L399


# Vulnerability details

## Impact
Payout to user fulfilling bid orders (by selling their NFTs) on ERC1155 will get incorrectly calculated. This will lead to eth getting stuck in the contract, and users not getting their advertised payout.

## Proof of Concept
This whole bug takes place in ```GolomTrader. _settleBalances ```, the ```protocolfee``` is calculated with the token amount (being traded) in the calculation (line 381 in ```GolomTrader.sol```), then is multiplied again by token amount when calculating the payout to the NFT seller on lines 390 and 397 in ```GolomTrader.sol```.

As a PoC, you can fulfill a bid order on an ERC1155 token with a token amount higher than 1, then the payout will be lower than expected because of the calculation mistake. 

## Recommended Mitigation Steps
Can easily fix by changing ```GolomTrader.sol L390-399```:
Original:
```
payEther(
                (o.totalAmt - protocolfee - o.exchange.paymentAmt - o.prePayment.paymentAmt - o.refererrAmt) *
                    amount -
                    p.paymentAmt,
                msg.sender
            );
        } else {
            payEther(
                (o.totalAmt - protocolfee - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount - p.paymentAmt,
                msg.sender
            );
```
fixed:
```
payEther(
                (o.totalAmt - o.exchange.paymentAmt - o.prePayment.paymentAmt - o.refererrAmt) *
                    amount - protocolfee -
                    p.paymentAmt,
                msg.sender
            );
        } else {
            payEther(
                (o.totalAmt - o.exchange.paymentAmt - o.prePayment.paymentAmt) * amount - protocolfee - p.paymentAmt,
                msg.sender
            );
```

Beyond this fix, I would recommend redoing most of the math throughout the code to take advantage of variables and use descriptive names.
Like ```protocolFeePerToken``` for fee representing one unit of the NFT for sale, then reusing this variable instead of copying and pasting ```(50 * o.totalAmt)/1000``` everywhere. I saw a lot of copy and pasted formulas, this is generally bad practice and leads to mistakes like above. Also makes it much harder to change things like protocolfee because it is calculated in many different spots.
Please consider making the calculations once and storing in variables, then using variables to fill in amount throughout the functions, this makes the math more readable and avoids mistakes like above.