xanoutas/solidity-security-findings huggingface dataset:
Denial of Service (DoS) attacks
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Position.sol#L109-L114


# Vulnerability details

## Impact
 The deny function can be called by anyone who passes the IReserve(zchf.reserve()).checkQualified(msg.sender, helpers) check. This could potentially lead to a situation where a malicious actor repeatedly calls this function to make the contract unusable for the owner.

## Proof of Concept

```
// A malicious actor could potentially call the `deny` function repeatedly
positionContract.deny(helpers, "Denial message");
```
 An attacker could repeatedly call the deny function with different helper addresses, which would cause the contract to enter cooldown and eventually become unusable for the owner.
## Recommended Mitigation Steps
Remediation Steps: Consider adding more access control mechanisms to the deny function, such as only allowing a specific set of authorized users (e.g., a multisig wallet) to call this function. One way to implement this would be to use OpenZeppelin's AccessControl contract. Example:

```
import "@openzeppelin/contracts/access/AccessControl.sol";

contract Position is Ownable, IPosition, MathUtil, AccessControl {
    bytes32 public constant DENY_ROLE = keccak256("DENY_ROLE");

    // Add the constructor code here

    // Modify the deny function
    function deny(address[] calldata helpers, string calldata message) public {
        require(hasRole(DENY_ROLE, msg.sender), "Caller is not authorized to deny");
        if (block.timestamp >= start) revert TooLate();
        IReserve(zchf.reserve()).checkQualified(msg.sender, helpers);
        cooldown = expiration;
        emit PositionDenied(msg.sender, message);
    }

    // Add a function to grant the DENY_ROLE to an address
    function grantDenyRole(address account) public onlyOwner {
        grantRole(DENY_ROLE, account);
    }

    // Add a function to revoke the DENY_ROLE from an address
    function revokeDenyRole(address account) public onlyOwner {
        revokeRole(DENY_ROLE, account);
    }
}
```
This implementation restricts the deny function to only be called by addresses with the DENY_ROLE. The contract owner can grant or revoke this role using the grantDenyRole and revokeDenyRole functions.
Math incentivizes MEV front-running in many ways and a user may grief below 1000 shares.
Audit: 2023-04-frankencoin-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L241-L270


# Vulnerability details

## Impact
1) ZCHF tokens received from exchanging shares decreases as the total number of shares decrease. This means that large exchanges being front-ran will be profitable.

2) ZCHF makes 1000 shares at the start, with only 1000 ZCHF tokens. The next 1000 ZCHF tokens only nets about 260 shares.

Both of these have the potential for an MEV competition for the first depositor & potentially griefing from there. 

Griefing can happen under 1000 shares. If there are less than 1000 total shares, then no matter how many shares you `should` get, you will get just enough to bring us up to 1000 total shares. Theoretically, we can grief the system pretty bad.

## Proof of Concept
Alice is the first depositor, and deposits 1000 ZCHF for 1000 shares.
Alice sees Bob is trying to deposit 1000 ZCHF
Alice front runs and burns 1 share
Bob deposits 1000 ZCHF successfully for only 1 share
Alice can then deposit 1000 ZCHF and receive over 100 shares.

```
    describe("exchanges shares & pricing", () => {
        it("redeem 1 share", async () => {
            let fAmount = floatToDec18(1000);// amount we will deposit
            await ZCHFContract.connect(accounts[0]).transferAndCall(equityContract.address, fAmount, 0);

            let BLOCK_MIN_HOLDING_DURATION = 90 * 7200;
            await network.provider.send("hardhat_mine", [BNToHexNoPrefix(BLOCK_MIN_HOLDING_DURATION + 1)]);
            let canRedeem = await equityContract.connect(accounts[0])['canRedeem()']();

            let fAmountShares = floatToDec18(1); // redeem 1 share

            await equityContract.redeem(owner, fAmountShares);

            // Deposit another 1000
            fAmount = floatToDec18(1000);// amount we will deposit
            await ZCHFContract.connect(accounts[1]).transferAndCall(equityContract.address, fAmount, 0);

            // Deposit another 1000
            await ZCHFContract.connect(accounts[0]).transferAndCall(equityContract.address, fAmount, 0);

            // Check that bob has only 1 share, although he deposited 1000 ZCHF
            const bobShares = floatToDec18(1);
            const aliceShares = floatToDec18(1100);
            expect(await equityContract.balanceOf(otherUser)).to.equal(bobShares);
            expect(await equityContract.balanceOf(owner)).to.be.gt(1100);
        });
    });
````


Clearly, this could get out of hand, and can largely throw the system out of balance.

## Tools Used
hardhat

## Recommended Mitigation Steps
Change https://github.com/code-423n4/2023-04-frankencoin/blob/main/contracts/Equity.sol#L241-L270 such that we do not have this occurring. It is difficult to say exactly what should be changed, but one simple solution is to only use calculateSharesInternal and have it work for totalShares < 1000.
Copy your own portfolio to keep earning royalties 
Audit: 2021-11-nested-findings
Severity: HIGH

# Handle

jayjonah8


# Vulnerability details

## Impact
In NestedFactory.sol going through the create() function which leads to the sendFeesWithRoyalties() => _addShares() function,  Im not seeing any checks preventing someone from copying their own portfolio and receiving royalty shares for it and simply repeating the process over and over again.  

## Proof of Concept
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/FeeSplitter.sol#L152
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/FeeSplitter.sol#L220


https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L103

https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedAsset.sol#L69

https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L103

https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L491



## Tools Used
Manual code review 

## Recommended Mitigation Steps
A require statement should be added not allowing users to copy their own portfolios.
Does not check uniqueness of ShareHolder
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

hack3r-0m


# Vulnerability details

https://github.com/code-423n4/2021-11-nested/blob/main/contracts/FeeSplitter.sol#L264


does not check if there is already a shareholder before creating a new shareholder.

this will cause an issue in `findShareHolder` since it will return the first shareholder in the array while there are more than one.
Passing multiple ETH deposits in orders array will use the same msg.value many times
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

hyh


# Vulnerability details

## Impact

Contract holdings can be emptied as malicious user will do deposit/withdraw to extract value. This is possible because after _transferInputTokens system uses contract balance for user's operations, assuming that equivalent value was transferred.

## Proof of Concept

msg.value persist over calls, so passing ```'Order[] calldata _orders'``` holding multiple ETH deposits will use the same msg.value in each of them, resulting in multiple deposits, that sums up to much bigger accounted value than actually deposited value, up to contract's ETH holdings.

create / addTokens -> _submitInOrders -> _transferInputTokens
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L103
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L119

sellTokensToWallet -> _submitOutOrders -> _transferInputTokens
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L172

sellTokensToNft -> _submitOutOrders -> _transferInputTokens
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L152

_transferInputTokens uses msg.value:
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L462


## Recommended Mitigation Steps

Controlling ETH to be only once in orders will not help, as NestedFactory inherits from Multicall, which multicall(bytes[] calldata data) function allows same reusage of msg.value, which will persist over calls.

So, it is recommended to treat ETH exclusively, not allowing ETH operations to be batched at all.
Ensure on-chain that cache is synced
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

GreyArt


# Vulnerability details

## Impact
Currently, many core operations (like NestedFactory.create(), NestedFactory.swapTokenForTokens()) are dependent on the assumption that the cache is synced before these functions are executed however this may not necessarily be the case.

## Proof of Concept
1. OperatorResolver.importOperators() is called to remove an operator.
2. A user calls NestedFactory.create() that uses the operator that was being removed / updated.
3. NestedFactory.rebuildCache() is called to rebuild cache.

This flow is not aware that the cache is not in synced.

## Recommended Mitigation Steps
Add a modifier to require that the cache is synced to all functions that interact with the operators.
`NestedFactory.sol#_submitInOrders()` Wrong implementation cause users to be overcharged
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

WatchPug


# Vulnerability details

When executing orders, the actual `amountSpent + feesAmount` can be lower than `_inputTokenAmount`, the unspent amount should be returned to the user.

However, in the current implementation, the unspent amount will be taken as part of the fee.

https://github.com/code-423n4/2021-11-nested/blob/f646002b692ca5fa3631acfff87dda897541cf41/contracts/NestedFactory.sol#L285-L309

```solidity=285
function _submitInOrders(
    uint256 _nftId,
    IERC20 _inputToken,
    uint256 _inputTokenAmount,
    Order[] calldata _orders,
    bool _reserved,
    bool _fromReserve
) private returns (uint256 feesAmount, IERC20 tokenSold) {
    _inputToken = _transferInputTokens(_nftId, _inputToken, _inputTokenAmount, _fromReserve);
    uint256 amountSpent;
    for (uint256 i = 0; i < _orders.length; i++) {
        amountSpent += _submitOrder(address(_inputToken), _orders[i].token, _nftId, _orders[i], _reserved);
    }
    feesAmount = _calculateFees(_msgSender(), amountSpent);
    assert(amountSpent <= _inputTokenAmount - feesAmount); // overspent

    // If input is from the reserve, update the records
    if (_fromReserve) {
        _decreaseHoldingAmount(_nftId, address(_inputToken), _inputTokenAmount);
    }

    _handleUnderSpending(_inputTokenAmount - feesAmount, amountSpent, _inputToken);

    tokenSold = _inputToken;
}
```

### Recommendation

Change to:

```solidity=285
function _submitInOrders(
    uint256 _nftId,
    IERC20 _inputToken,
    uint256 _inputTokenAmount,
    Order[] calldata _orders,
    bool _reserved,
    bool _fromReserve
) private returns (uint256 feesAmount, IERC20 tokenSold) {
    _inputToken = _transferInputTokens(_nftId, _inputToken, _inputTokenAmount, _fromReserve);
    uint256 amountSpent;
    for (uint256 i = 0; i < _orders.length; i++) {
        amountSpent += _submitOrder(address(_inputToken), _orders[i].token, _nftId, _orders[i], _reserved);
    }
    feesAmount = _calculateFees(_msgSender(), amountSpent);
    assert(amountSpent <= _inputTokenAmount - feesAmount); // overspent

    // If input is from the reserve, update the records
    if (_fromReserve) {
        _decreaseHoldingAmount(_nftId, address(_inputToken), amountSpent+feesAmount);
    }

    ExchangeHelpers.setMaxAllowance(_token, address(feeSplitter));
    feeSplitter.sendFees(_token, feesAmount);

    if (_inputTokenAmount > amountSpent + feesAmount) {
        _inputToken.transfer(_fromReserve ? address(reserve) : _msgSender(), _inputTokenAmount - amountSpent - feesAmount);
    }

    tokenSold = _inputToken;
}
```
`MixinOperatorResolver.sol#isResolverCached()` become malfunctioning when an `operator` is removed
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

WatchPug


# Vulnerability details

When an `operator` is removed and `rebuildCache()` is called, `isResolverCached()` should return `true`. It returns `false` in the current implemenbtation.

https://github.com/code-423n4/2021-11-nested/blob/f646002b692ca5fa3631acfff87dda897541cf41/contracts/MixinOperatorResolver.sol#L45-L56

```solidity=45{51}
/// @notice Check the state of addressCache
function isResolverCached() external view returns (bool) {
    bytes32[] memory requiredAddresses = resolverAddressesRequired();
    for (uint256 i = 0; i < requiredAddresses.length; i++) {
        bytes32 name = requiredAddresses[i];
        // false if our cache is invalid or if the resolver doesn't have the required address
        if (resolver.getAddress(name) != addressCache[name] || addressCache[name] == address(0)) {
            return false;
        }
    }
    return true;
}
```

Beacuse when an `operator` is removed, `requiredAddresses` will includes empty items, so that `addressCache[name]` will be `address(0)`, and `return false`.

### Recommendation

Change to:

```solidity=45{51}
/// @notice Check the state of addressCache
function isResolverCached() external view returns (bool) {
    bytes32[] memory requiredAddresses = resolverAddressesRequired();
    for (uint256 i = 0; i < requiredAddresses.length; i++) {
        bytes32 name = requiredAddresses[i];
        // false if our cache is invalid or if the resolver doesn't have the required address
        if (resolver.getAddress(name) != addressCache[name]) {
            return false;
        }
    }
    return true;
}
```
`importOperators()` should be run atomically with `rebuildCaches()` to make sure `addressCache` is up-to-date with the new `operators`
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

WatchPug


# Vulnerability details

Given that `importOperators()` will change `operators`, and `addressCache` will not be updated until `rebuildCaches()` is called separately.

To ensure `addressCache` is up-to-date, `importOperators()` should be run atomically with `rebuildCaches()`.

### Recommendation

https://github.com/code-423n4/2021-11-nested/blob/f646002b692ca5fa3631acfff87dda897541cf41/contracts/OperatorResolver.sol#L42-L51

Consider changing `importOperators()` to:

```solidity
function importOperators(bytes32[] calldata names, address[] calldata operators, MixinOperatorResolver[] calldata destinations) external override onlyOwner {
    require(names.length == operators.length, "OperatorResolver::importOperators: Input lengths must match");

    for (uint256 i = 0; i < names.length; i++) {
        bytes32 name = names[i];
        address operator = operators[i];
        operators[name] = operator;
        emit OperatorImported(name, operator);
    }
    _rebuildCaches(destinations);
}
```
isResolverCached() will always return false after removing operator
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

GreyArt


# Vulnerability details

## Impact

While there is no loss of funds, removing an operator will cause the cache functionality to be permanently broken. If there was a function that had a modifier which requires the cache to be synced before the function can be called, it would not be callable as well.

The underlying issue is how the `bytes32` operator is removed from the array when `removeOperator()` is called. Its value gets deleted (set to 0x0), but isn't taken out of the array.

## Proof of Concept

For ease of reading, we use abbreviated strings like `0xA`, `0xB` for `bytes32` and `address` types.

1. Import 3 operators by calling `OperatorResolver.importOperators([0xA, 0xB, 0xC], [0x1, 0x2, 0x3)`. 
2. Call `NestedFactory.addOperator()` 3 times to push these 3 operators into the `operators` state variable.
3. Call `NestedFactory.rebuildCache()` to build the cache.
4. Let's say the second operator `0xB` is to be removed. Taking reference from the `removeOperator.ts` script, `OperatorResolver.importOperators([0xA, 0xB, 0xC], [0x1, 0x2, 0x3)` is called first. This works because OperatorResolver uses a mapping(bytes32 ⇒ address) to represent the operators. Hence, by setting `0xB`'s destination address to be the null address, it is like as if he was never an operator.
5. Call `NestedFactory.rebuildCache()` to rebuild the cache. `resolverAddressesRequired()` will return `[0xA, 0xB, 0xC]`. `0xB` will be removed from `addressCache` because `resolver.getAddress(0xB)` returns 0x000 since it has been deleted from the OperatorResolver.
6. Call `NestedFactory.removeOperator(0xB)`. The `operators` array now looks like this: `[0xA, 0x0, 0xC]`.
7. When you try to call `NestedFactory.isResolverCached`, it will always return false because of the null `bytes32` value, where `addressCache[0x0]` will always return the null address.

## Recommended Mitigation Steps

Instead of doing an element deletion, it should be replaced with the last element, then have the last element popped in `removeOperator()`.

```jsx
function removeOperator(bytes32 operator) external override onlyOwner {
	for (uint256 i = 0; i < operators.length; i++) {
		if (operators[i] == operator) {
			operators[i] = operators[operators.length - 1];
			operators.pop();
			break;
		}
	}
}
```
FeeSplitter: Unbounded number of shareholders can cause DOS
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

GreyArt


# Vulnerability details

## Impact

There is no limit to the number of shareholders. It is therefore possible to set a large number of shareholders such that `_sendFees()` will run out of gas when adding shares to each shareholder.
This will cause denial of service to all NestedFactory functions, especially the ones that will remove funds like `withdraw()` and `destroy()`.

## Recommended Mitigation Steps

It would be best to set a sanity maximum number of shareholders that can be added.
NestedFactory: Ensure zero msg.value if transferring from user and inputToken is not ETH 
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

GreyArt


# Vulnerability details

## Impact

A user that mistakenly calls either `create()` or `addToken()` with WETH (or another ERC20) as the input token, but includes native ETH with the function call will have his native ETH permanently locked in the contract.

## Recommended Mitigation Steps

It is best to ensure that `msg.value = 0` in `_transferInputTokens()` for the scenario mentioned above.

```jsx
} else if (address(_inputToken) == ETH) {
	...
} else {
	require(msg.value == 0, "NestedFactory::_transferInputTokens: ETH sent for non-ETH transfer");
  _inputToken.safeTransferFrom(_msgSender(), address(this), _inputTokenAmount);
}
```
FeeSplitter: No sanity check to prevent shareholder from being added twice.
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

GreyArt


# Vulnerability details

## Impact

It is possible for duplicate shareholders to be added. These shareholders will get more than intended when `_sendFee()` is called.

## Recommended Mitigation Steps

Ensure that the `_accounts` array is sorted in `setShareholders()`.

```jsx
for (uint256 i = 0; i < _accounts.length; i++) {
	if (i > 0) {
		require(_accounts[i - 1] < _accounts[i], "FeeSplitter: ACCOUNTS_NOT_SORTED");
	}
	_addShareholder(_accounts[i], _weights[i]);
}
```
setReserve() can be front-run
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

palina


# Vulnerability details

## Impact
The `reserve` address variable in NestedFactory.sol remains equal to 0 before the setReserve() function is called by an owner. This may lead to incorrect transfers of tokens or invalid comparison with e.g., the asset reserve (nestedRecords.getAssetReserve(_nftId) == address(reserve)), should they occur before the value for `reserve` was set.
In addition, the immutabiliy of the `reserve` variable requires extra caution when setting the value.

## Proof of Concept
setReserve(): https://github.com/code-423n4/2021-11-nested/blob/5d113967cdf7c9ee29802e1ecb176c656386fe9b/contracts/NestedFactory.sol#L89

## Tools Used
Manual Analysis

## Recommended Mitigation Steps
Consider initializing the value for the `reserve` variable in the constructor.
Multicall.sol batch calls don't update msg.value
Audit: 2021-11-nested-findings
Severity: MEDIUM

# Handle

jayjonah8


# Vulnerability details

## Impact
In NestedFactory.sol using Multicall.sol can be dangerous when it has a msg.value inside a loop since the msg.value doesn't update every iteration.  This can lead to a user sending ETH one time and it being counted for every iteration.  There is a msg.value in create() => _submitOrders => _transferInputTokens() which simply changes ETH for WETH. This _transferInputTokens() function is called alot in NestedFactory.sol by many functions.  This can introduce serious bugs in the future as the protocol grows.  

## Proof of Concept
https://github.com/code-423n4/2021-11-nested/blob/main/contracts/NestedFactory.sol#L6

https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/utils/Multicall.sol

https://samczsun.com/two-rights-might-make-a-wrong/

## Tools Used
Manual code review 

## Recommended Mitigation Steps
Remove the Open Zeppelin Multicall.sol functionality since it doesn't seem to be used and can introduce serious future bugs.
Nonce handling behavior is different from the one specified in the EVM
Audit: 2024-11-nibiru-findings
Severity: HIGH

# Lines of code

https://github.com/code-423n4/2024-11-nibiru/blob/main/x/evm/keeper/msg_server.go#L330


# Vulnerability details

## Finding description and impact

In the current implementation of the `msg.server`, the nonce is not incremented during the contract creation but during the transaction processing inside of the `ApplyEvmMsg()` function. This leads to a deviation from the EVM nonce behavior and makes the NibiruVM not compatible.

## Proof of Concept

Let's take a look at how nonces are currently handled:

https://github.com/code-423n4/2024-11-nibiru/blob/main/x/evm/keeper/msg_server.go#L319-339
```
if contractCreation {
		// take over the nonce management from evm:
		// - reset sender's nonce to msg.Nonce() before calling evm.
		// - increase sender's nonce by one no matter the result.
		stateDB.SetNonce(sender.Address(), msg.Nonce())
		ret, _, leftoverGas, vmErr = evmObj.Create(
			sender,
			msg.Data(),
			leftoverGas,
			msgWei,
		)
		stateDB.SetNonce(sender.Address(), msg.Nonce()+1)
	} else {
		ret, leftoverGas, vmErr = evmObj.Call(
			sender,
			*msg.To(),
			msg.Data(),
			leftoverGas,
			msgWei,
		)
	}

```

As you can see, the nonce is not increased during the contract creation but inside of the `ApplyEvmMsg()` function. Moreover, the nonce during the ordinary call is not increased at all. In comparison, take a look at the EVM implementation:

https://github.com/NibiruChain/go-ethereum/blob/nibiru/geth/core/state_transition.go#L331-337
```
	if contractCreation {
		ret, _, st.gas, vmerr = st.evm.Create(sender, st.data, st.gas, st.value)
	} else {
		// Increment the nonce for the next transaction
		st.state.SetNonce(msg.From(), st.state.GetNonce(sender.Address())+1)
		ret, st.gas, vmerr = st.evm.Call(sender, st.to(), st.data, st.gas, st.value)
	}
```

Here it correctly increments the nonce of the `sender` before the call and for the `contractCreation` inside of the `Create()` function.

## Recommended mitigation steps

Apply the differences in the nonce handling behavior mentioned above to be compatible with EVM.