xanoutas/solidity-security-findings huggingface dataset:
`CREATE2` address is predictable
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/ERC725Alliance/ERC725/blob/31574e1ac5259713e83c1b299800401a0737d483/implementations/contracts/ERC725XCore.sol#L296-L299


# Vulnerability details

## Impact
Similar to this issue:
https://github.com/code-423n4/2023-04-caviar-findings/issues/419

The address of the created contract is predictable and this causes two issues, as mentioned in the above issue:

1. Stealing deposited amount.
If a user creates a new contract and funds it in two separate txs. The first that creates the contract could be frontrun to steal the deposit. Since the contract creation tx would fail but the deposit tx would still succeed.

2. DoS of creating the contract.
A user who creates a contract can be front run by another user who creates the same contract. Thus the first users contract will not be created. This can be repeated denying them to create a contract.

Since we have no idea of what contracts will be deployed by `ERC725` any of these could be viable. If any of them sets `msg.origin` as owner or privileged role this will apply.

Any user abusing this would need the permission to create contracts but this is a pretty non-critical permission so it could be handed out very freely.

## Proof of Concept
https://github.com/ERC725Alliance/ERC725/blob/31574e1ac5259713e83c1b299800401a0737d483/implementations/contracts/ERC725XCore.sol#L296-L299
```solidity
File: implementations/contracts/ERC725XCore.sol

296:        bytes32 salt = BytesLib.toBytes32(
297:            creationCode,
298:            creationCode.length - 32
299:        );
```

Only publicly reproducible parameters are used in the salt. Hence this can be copied when the tx is in the mem pool.

## Tools Used
Manual audit

## Recommended Mitigation Steps
Consider adding `msg.sender`/`msg.origin` to the salt.


## Assessed type

Timing
receiving funds through `LSP1UniversalReceiverDelegateUP` can fail
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/main/contracts/LSP5ReceivedAssets/LSP5Utils.sol#L74-L83
https://github.com/code-423n4/2023-06-lukso/blob/main/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol#L180-L181


# Vulnerability details

## Impact
In certain wrongly configured circumstances a contract can fail to receive funds. Either if the storage is malformed or if the `LSP1UniversalReceiverDelegateUP` contract has the wrong permissions. Permissions can be fiddly to setup, especially if the goal is to keep with best practice and only allow least possible permissions.

Hence if in any way this is wrong the account will fail to receive funds. If the sender doesn't resend this the account has missed out on this forever.

## Proof of Concept
When receiving assets in `LSP1UniversalReceiverDelegateUP` there are a couple of steps that can fail, first one being if the `receivedAssetsCount` is malformed:

https://github.com/code-423n4/2023-06-lukso/blob/main/contracts/LSP5ReceivedAssets/LSP5Utils.sol#L74-L83
```solidity
File: LSP5ReceivedAssets/LSP5Utils.sol

74:        bytes memory encodedArrayLength = getLSP5ReceivedAssetsCount(account);
75:
76:        // CHECK it's either the first asset received,
77:        // or the storage is already set with a valid `uint128` value
78:        if (encodedArrayLength.length != 0 && encodedArrayLength.length != 16) {
79:            revert InvalidLSP5ReceivedAssetsArrayLength({
80:                invalidValueStored: encodedArrayLength,
81:                invalidValueLength: encodedArrayLength.length
82:            });
83:        }
```

And later if the `LSP1UniversalReceiverDelegateUP` contract fails to write the keys to the account storage:

https://github.com/code-423n4/2023-06-lukso/blob/main/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol#L180-L181
```solidity
File: LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol

180:            // Set the LSP5 generated data keys on the account
181:            IERC725Y(msg.sender).setDataBatch(dataKeys, dataValues);
```

This fails if the contract lacks the necessary permissions.

## Tools Used
Manual audit

## Recommended Mitigation Steps
Consider not reverting on malformed storage and doing the storage call in a `try`. If any of these fails, an event can be emitted and the erroneous configuration/storage can be fixed manually.

If any of these fail, the impact will be that the internal accounting of receiving these funds will be off, as the record of these could not be written. This is favorable to not receiving them at all as the storage can be manually fixed by the owner and made to reflect the current accounting of the contract.


## Assessed type

Token-Transfer
Ownership Transference Not a Two-Step Process in OwnableUnset Contract
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/ERC725Alliance/ERC725/blob/7171a0e25e83cfe4c4dec6262bb62b4422c0478f/implementations/contracts/custom/OwnableUnset.sol#L46


# Vulnerability details

## Impact

The OwnableUnset contract's current implementation does not enforce a two-step ownership transfer process, which is typically used to enhance the security of the contract. The two-step process usually consists of 1) the current owner proposing a new owner and 2) the new owner accepting the ownership. This process reduces the chances of transferring the ownership to a wrong or malicious address due to an error or attack.

In the OwnableUnset contract, the transferOwnership(address newOwner) function immediately transfers the ownership to the newOwner as long as the caller of the function is the current owner and the newOwner is not the zero address. This one-step process could pose a security risk.

```
function transferOwnership(address newOwner) public virtual onlyOwner {
    require(newOwner != address(0), "Ownable: new owner is the zero address");
    _setOwner(newOwner);
}
```

Suggested Fix:
To incorporate a two-step process, the contract can introduce an additional state variable _proposedOwner, which can be set in transferOwnership(address newOwner). The _proposedOwner can then claim the ownership using a separate function acceptOwnership(). Here's an example of how it could be implemented:

```
address private _proposedOwner;

function transferOwnership(address newOwner) public virtual onlyOwner {
    require(newOwner != address(0), "Ownable: new owner is the zero address");
    _proposedOwner = newOwner;
}

function acceptOwnership() public virtual {
    require(msg.sender == _proposedOwner, "Ownable: caller is not the proposed owner");
    _setOwner(_proposedOwner);
    _proposedOwner = address(0);
}
```

In this proposed change, the current owner "proposes" a new owner by calling transferOwnership(). The proposed new owner then "accepts" the ownership by calling acceptOwnership(). This helps to reduce the risk of accidental or erroneous ownership transfers.

Severity: Medium

This issue may lead to potential ownership hijack if the contract owner accidentally inputs an incorrect or malicious address while calling the transferOwnership() function. The severity can be classified as medium as it could lead to unauthorized control over the contract, but it can only be exploited due to the owner's error, not by external attackers.


## Assessed type

Other
The transferOwnership function did not check if the newOwner address is 0, resulting in unexpected behavior.
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol#L66-L82
https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol#L126-L131


# Vulnerability details

## Impact
The transferOwnership function did not check if the newOwner address is 0, resulting in unexpected behavior.

## Proof of Concept
https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol#L66-L82
    function transferOwnership(
        address newOwner
    ) public virtual override(OwnableUnset, ILSP14Ownable2Step) onlyOwner {
        _transferOwnership(newOwner);

        address currentOwner = owner();
        emit OwnershipTransferStarted(currentOwner, newOwner);

        newOwner.tryNotifyUniversalReceiver(
            _TYPEID_LSP14_OwnershipTransferStarted,
            ""
        );
        require(
            currentOwner == owner(),
            "LSP14: newOwner MUST accept ownership in a separate transaction"
        );
    }

https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP14Ownable2Step/LSP14Ownable2Step.sol#L126-L131
    function _transferOwnership(address newOwner) internal virtual {
        if (newOwner == address(this)) revert CannotTransferOwnershipToSelf();

        _pendingOwner = newOwner;
        delete _renounceOwnershipStartedAt;
    }
## Tools Used
vscode
## Recommended Mitigation Steps
add check





## Assessed type

Other
Function collision between extensions, ADDEXTENSIONS can not add extension.
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol#L490-L499
https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol#L302-L318


# Vulnerability details

## Summary

Adding extension use 4 bytes function selector to add new extension, but if there is extension with those function selector, user with ADDEXTENSIONS permission cannot add extension.

## Impact
- ADDEXTENSIONS can not add extension.
## Proof of Concept
when a user with ADDEXTENSIONS permisson want to add new extensions LSP6SetDataModule.sol checks that user have both permissions or not, if user have one of them it checks that is there any extension with this function selector, if yes it needs _PERMISSION_CHANGEEXTENSIONS and user with ADDEXTENSIONS permisson cannot add extension.
```solidity
    function _getPermissionToSetLSP17Extension(
        address controlledContract,
        bytes32 lsp17ExtensionDataKey
    ) internal view virtual returns (bytes32) {
        return
            ERC725Y(controlledContract).getData(lsp17ExtensionDataKey).length ==
                0
                ? _PERMISSION_ADDEXTENSIONS
                : _PERMISSION_CHANGEEXTENSIONS;
    }
```

## Tools Used

manual

## Recommended Mitigation Steps
save all functions selector and who want to add extension, check for collision before deploying extension





## Assessed type

Other
tokens owned by the account and tokens in ERC725Y might be different
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol#L426-L427


# Vulnerability details

## Summary

if the owner is LSP6 we use Universal Receiver Delegate to add asset owned by account to ERC725Y,
but if the user is EOA there is not such a thing,

## Impact

asset might be different in ERC725Y from original one that owned by account

## Proof of Concept

see next steps:

1.  account owned by EOA receive some assets
2.  transfer ownership to LSP6
3.  there will be difference between ERC725Y and original asset.

## Tools Used

manual

## Recommended Mitigation Steps

If account is owned by EOA there will be mechanism that listen to event and add asset to ERC725Y or delete from that or after transferring to LSP6 check last emits and add changes to ERC725Y.


## Assessed type

Other
Incorrect Removal of Operators in _clearOperators Function
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/main/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol#L258


# Vulnerability details

## Impact
The bug in the _clearOperators function can result in incorrect removal and skipping of operators. This issue affects the contract's ability to properly clear all operators associated with a specific tokenId. It can lead to operators being unintentionally retained or operators being mistakenly removed.

# BUG Explanation

The _clearOperators function in the provided code has a flaw in the removal process. The function attempts to clear all operators for a given tokenId, but it always removes the operator at index 0. This approach causes subsequent operators to shift down by one index with each iteration. However, the loop variable i is not adjusted accordingly, leading to skipping operators and incomplete removal.

## Proof of Concept
To demonstrate the bug, consider the following test scenario:

pragma solidity ^0.8.4;

contract LSP8IdentifiableDigitalAssetCore {
    // Mapping a `tokenId` to its authorized operator addresses.
    mapping(bytes32 => address[]) private _operators;

    function _clearOperators(
        address tokenOwner,
        bytes32 tokenId
    ) internal virtual {
        EnumerableSet.AddressSet storage operatorsForTokenId = _operators[tokenId];

        uint256 operatorListLength = operatorsForTokenId.length();
        for (uint256 i = 0; i < operatorListLength; ) {
            // we are emptying the list, always remove from index 0
            address operator = operatorsForTokenId.at(0);
            _revokeOperator(operator, tokenOwner, tokenId);

            unchecked {
                ++i;
            }
        }
    }

    // Helper function to revoke an operator (not included in the PoC)
    function _revokeOperator(
        address operator,
        address tokenOwner,
        bytes32 tokenId
    ) internal virtual {
        // Perform the necessary revocation steps
        // ...
    }
}

contract LSP8IdentifiableDigitalAssetCoreTest {
    LSP8IdentifiableDigitalAssetCore contractToTest;

    constructor() {
        contractToTest = new LSP8IdentifiableDigitalAssetCore();
    }

    function testClearOperators() public {
        bytes32 tokenId = bytes32(0x123456789);
        contractToTest._operators[tokenId].push(address(0xAAA));
        contractToTest._operators[tokenId].push(address(0xBBB));
        contractToTest._operators[tokenId].push(address(0xCCC));

        contractToTest._clearOperators(address(0xOwner), tokenId);

        // Verify that operators are not cleared properly
        require(contractToTest._operators[tokenId].length > 0, "Operators were not cleared correctly");
    }
}


Executing the testClearOperators function in the LSP8IdentifiableDigitalAssetCoreTest contract will demonstrate the issue. The function adds three operators to a token and then attempts to clear them using the _clearOperators function. H
Asset addition is not carried out for upward overflow prevention
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/bd49f57c32a522563fc42feeee23c83c8b373405/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol#L210
https://github.com/code-423n4/2023-06-lukso/blob/bd49f57c32a522563fc42feeee23c83c8b373405/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol#L311
https://github.com/code-423n4/2023-06-lukso/blob/bd49f57c32a522563fc42feeee23c83c8b373405/contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol#L433
https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol#L332
https://github.com/code-423n4/2023-06-lukso/blob/9dbc96410b3052fc0fd9d423249d1fa42958cae8/contracts/LSP8IdentifiableDigitalAsset/LSP8IdentifiableDigitalAssetCore.sol#L366


# Vulnerability details

## Impact
causing confusion in the amount of assets

## Proof of Concept

uint256 a;
uint256 b;
uint256 c;

a = b+c;

If overflow occurs a<b or a<c

## Tools Used

manual review

## Recommended Mitigation Steps

import "@openzeppelin/contracts/utils/math/SafeMath.sol";

contract MyContract {
    using SafeMath for uint256;

    uint256 public myNumber;

    function addToNumber(uint256 _value) public {
        myNumber = myNumber.add(_value);
    }
}











## Assessed type

Under/Overflow
receive() needs to emit the value it receives
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/bd49f57c32a522563fc42feeee23c83c8b373405/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol#L25


# Vulnerability details

## Impact
Detailed description of the impact of this finding.
the receive functions does not emit any amount sent to it
## Proof of Concept
Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.
https://github.com/code-423n4/2023-06-lukso/blob/bd49f57c32a522563fc42feeee23c83c8b373405/contracts/Mocks/Reentrancy/SingleReentrancyRelayer.sol#L25
    receive() external payable {}
## Tools Used
vscode
## Recommended Mitigation Steps
 event ValueReceived(uint256);
receive() external payable {
        emit ValueReceived(msg.value);
    }


## Assessed type

Context
Permission escalation by adding the same permission twice
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/lukso-network/lsp-smart-contracts/blob/v0.10.2/contracts/LSP6KeyManager/LSP6Utils.sol#L169-L177


# Vulnerability details

## Impact
The function `combinePermissions` adds permission to availabe permissions.
If the same permission is added twice, then this will result in a new and different permission.
For example adding `_PERMISSION_STATICCALL` twice results in `_PERMISSION_SUPER_DELEGATECALL`.

This way accidentally dangerous permissions can be set.
Once someone has a dangerous permission, for example `_PERMISSION_SUPER_DELEGATECALL`, they can change all storage parameters of the Universion Profile and then steal all the assets.

## Proof of Concept
The following code shows this. Run with `forge test -vv` to see the console output.

```solidity
pragma solidity ^0.8.13;

import "../../contracts/LSP6KeyManager/LSP6KeyManager.sol";
import "../../contracts/LSP0ERC725Account/LSP0ERC725Account.sol";
import "../../contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol";
import "../../contracts/LSP6KeyManager/LSP6Constants.sol";
import "./UniversalProfileTestsHelper.sol";

contract SetDataRestrictedController is UniversalProfileTestsHelper {
    LSP0ERC725Account public mainUniversalProfile;
    LSP6KeyManager public keyManagerMainUP;
    address public mainUniversalProfileOwner;
    address public combineController;

    function setUp() public {
        mainUniversalProfileOwner = vm.addr(1);
        vm.label(mainUniversalProfileOwner, "mainUniversalProfileOwner");
        combineController = vm.addr(10);
        vm.label(combineController, "combineController");
        mainUniversalProfile = new LSP0ERC725Account(mainUniversalProfileOwner);

        // deploy LSP6KeyManagers
        keyManagerMainUP = new LSP6KeyManager(address(mainUniversalProfile));
        transferOwnership(
            mainUniversalProfile,
            mainUniversalProfileOwner,
            address(keyManagerMainUP)
        );        
    }
    
    function testCombinePermissions() public {       
        bytes32[] memory ownerPermissions = new bytes32[](3);
        ownerPermissions[0] = _PERMISSION_STATICCALL;
        ownerPermissions[1] = _PERMISSION_STATICCALL;
        givePermissionsToController(
            mainUniversalProfile,
            combineController,
            address(keyManagerMainUP),
            ownerPermissions
        );        
        bytes32 key = LSP2Utils.generateMappingWithGroupingKey(_LSP6KEY_ADDRESSPERMISSIONS_PERMISSIONS_PREFIX,bytes20(combineController));
        bytes memory r = mainUniversalProfile.getData(key);
        console.logBytes(r); // 0x00..4000  SUPER_DELEGATECALL
    }
}
```
See [LSP6Utils.sol#L169-L177](https://github.com/lukso-network/lsp-smart-contracts/blob/v0.10.2/contracts/LSP6KeyManager/LSP6Utils.sol#L169-L177) for the code of `combinePermissions`.

## Tools Used
Foundry

## Recommended Mitigation Steps
The permissions shouldn't be added, but they should be `OR`d.
Here is a way to solve this:
```diff
"universalReceiver" in "UniversalReceiverDelegate" could be directly called, leading to data manipulation
Audit: 2023-06-lukso-findings
Severity: MEDIUM

# Lines of code

https://github.com/code-423n4/2023-06-lukso/blob/bd49f57c32a522563fc42feeee23c83c8b373405/contracts/LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol#L78-L81


# Vulnerability details

## Background
*One way to solve this problem is by creating a standard and unified function that any smart contract can implement. DEXs, Wallets, or profiles could use this function to be notified about an incoming asset, information, followers, etc.*

## Impact
If the owner of contract A wishes to authorize a user to send information or notify other contracts on behalf of A, they would grant the user permission to call function `universalReceiver` of other contracts (on behalf of A). However, the user may use this permission in an unintended way by directly calling function `universalReceiver` in the `UniversalReceiverDelegateUP` contract (on behalf of A). This could lead to arbitrary manipulation of A's storage, even if the user does not have permission to set data.

## Proof of Concept
    // in contract A
    function universalReceiver(
        bytes32 typeId,
        bytes calldata receivedData
    ) public payable virtual returns (bytes memory returnedValues) {
            ......
            if (universalReceiverDelegate.supportsERC165InterfaceUnchecked(_INTERFACEID_LSP1)) {
                resultDefaultDelegate = universalReceiverDelegate
                    .callUniversalReceiverWithCallerInfos(
                        typeId,
                        receivedData,
                        msg.sender,
                        msg.value
                    );
            }
            ......
    }

    function callUniversalReceiverWithCallerInfos(
        address universalReceiverDelegate,
        bytes32 typeId,
        bytes calldata receivedData,
        address msgSender,
        uint256 msgValue
    ) internal returns (bytes memory) {
        bytes memory callData = abi.encodePacked(
            abi.encodeWithSelector(
                ILSP1UniversalReceiver.universalReceiver.selector,
                typeId,
                receivedData
            ),
            msgSender,
            msgValue
        );

        (bool success, bytes memory result) = universalReceiverDelegate.call(callData);
        ......
    }

    // in contract UniversalReceiverDelegateUP
    function universalReceiver(
        bytes32 typeId,
        bytes memory /* data */
    ) public payable virtual returns (bytes memory result) {
        if (msg.value != 0) revert NativeTokensNotAccepted();

        // This contract acts like a UniversalReceiverDelegate of an LSP0ERC725Account where we append the
        // address and the value, sent to the universalReceiver function of the LSP0, to the msg.data
        // Check https://github.com/lukso-network/LIPs/blob/main/LSPs/LSP-0-ERC725Account.md#universalreceiver
        address notifier = address(bytes20(msg.data[msg.data.length - 52:]));
        ......
    }

The correct call pa