Platform: Code4rena
Start Date: 30/06/2023
Pot Size: $100,000 USDC
Total HM: 8
Participants: 22
Period: 14 days
Judge: Trust
Total Solo HM: 6
Id: 253
League: ETH
Rank: 17/22
Findings: 1
Award: $60.20
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: gpersoon
Also found by: DavidGiladi, MiloTruck, Rolezn, banpaleo5, catellatech, matrix_0wl, naman1778, vnavascues
60.1995 USDC - $60.20
The current best practices for transferring the ownership is a 2-step process (as an example, see OpenZeppelin Ownable2Step.transferOwnership
and Vectorized Solady Ownable.transferOwnership
). In the same way, best practices for renouncing the ownership is a 2-step process, although the most common access control libraries still implement it in a single-step fashion (as an example, see OpenZeppelin Ownable2Step.renounceOwnership
(from Ownable.renounceOwnership
) and Vectorized Solady Ownable.renounceOwnership
). And on this single-step implementation, renouncing the ownership does not interfere with transferring the ownership, which allows to:
1. Owner transfers the ownership to newOwner (as pending owner). 2. Owner renounces the ownership (Zero address is the owner, newOwner is the pending owner). 3. newOwner accepts the ownership (from pending owner to owner).
Lukso rightfully implements a 2-step renounce ownership process (one of the two options suggested by Watchpug on LUKSO LSPs#2 Audit Report - [WP‐L6] LSP‐14: _pendingOwner
should be deleted when _renounceOwnership
is called for the first time to initialize it), however it interfers with a transferring ownership process and vice-versa:
transferOwnership()
resets a rounouncing ownership process by deleting _renounceOwnershipStartedAt
.renounceOwnership()
resets a transferring ownership process by deleting _pendingOwner
.This has a different outcome for the case described above:
1. Owner transfers the ownership to newOwner (as pending owner). 2. Owner renounces the ownership: 2.1. Owner makes the 1st step for renouncing the ownership (Zero address is the pending owner). 2.2. Owner makes the 2nd step for renouncing the ownership (Zero address is the owner and the pending owner). 3. newOwner can't accept the ownership (the contract is left without owner).
Lukso's ILSP14Ownable2Step
contract NatSpec and LSP14Ownable2Step Official docs warn about the renounceOwnership()
dangers and also inform about the pendingOwner
value on different scenarios, but none of them mention the fact that transferOwnership()
& renounceOwnership()
cancel each other nor the differences with the single-step process common in the industry.
Explain in greater detail (in the LSP14Ownable2Step
module NatSpec and the official website docs) how transferOwnership()
and renounceOwnership()
interact with each other and warn about the differences between Lukso's 2-step process and the common single-step one.
Alternatively, for extra on-chain safety, consider introducing a lock to prevent to transfer the ownership during an ownership renouncement and vice-versa (already suggested by Watchpug on LUKSO LSPs#2 Audit Report - [WP‐L6] LSP‐14: _pendingOwner
should be deleted when _renounceOwnership
is called for the first time to initialize it but discarded by Lukso).
https://github.com/code-423n4/2023-06-lukso/blob/09bbdd68eeba6ed4dd624286c94a1947f79c1959/contracts/LSP14Ownable2Step/ILSP14Ownable2Step.sol#L44-L54 https://github.com/code-423n4/2023-06-lukso/blob/09bbdd68eeba6ed4dd624286c94a1947f79c1959/contracts/LSP14Ownable2Step/ILSP14Ownable2Step.sol#L68-L76
_verifyCanSetData
reverts with panic if inputDataKeys
or inputDataValues
is an empty arrayFile: contracts/LSP6KeyManager/LSP6Modules/LSP6SetDataModule.sol
The function LSP6SetDataModule._verifyCanSetData
reverts with panic 0x32 (Array accessed at an out-of-bounds or negative index)
if inputDataKeys
or inputDataValues
is an empty array because of the do..while
loop first iteration.
Currently, there is no risk for the LSP6KeyManager
because it is exclusively called by LSP6KeyManagerCore._verifyPermissions
when data has to be set in batch (and it does not make any sense to set no data). However, future extensions of LSP6KeyManagerCore
should be aware of it.
Revert if inputDataKeys
or inputDataValues
length is zero.
File: contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol // Description if (inputDataKeys.length != inputDataValues.length) { revert ERC725Y_DataKeysValuesLengthMismatch(); } // Recommendation uint256 inputDataKeysLength = inputDataKeys.length; if (inputDataKeys.length == 0) { revert ERC725Y_DataKeysValuesIsEmpty(); } uint256 inputDataValuesLength = inputDataValues.length; if (inputDataKeysLength != inputDataValuesLength) { revert ERC725Y_DataKeysValuesLengthMismatch(); }
unchecked
block by wrapping the entire (_updateOperator
) functionFuture changes on _updateOperator
could under- or overflow.
File: contracts/LSP7DigitalAsset/LSP7DigitalAssetCore.sol // Description unchecked { _updateOperator( msg.sender, operator, currentAllowance - substractedAmount ); } // Recommendation uint256 updatedAllowance; unchecked { updatedAllowance = currentAllowance - substractedAmount; } _updateOperator( msg.sender, operator, updatedAllowance );
There is 1 instance of this issue:
<details> <summary>see instances</summary></details>File: contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol // Description require(dataKey.length >= 2, "MUST be longer than 2 characters"); // Recommendation require(dataKey.length >= 2, "MUST be longer than 1 character"); // "LSP2: must be >= 2 characters"
There is 1 instance of this issue:
<details> <summary>see instances</summary></details>File: contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol // Description bytes32 hashFunctionDigest = keccak256(bytes(hashFunction)); bytes32 jsonDigest = keccak256(bytes(assetBytes)); return abi.encodePacked(bytes4(hashFunctionDigest), jsonDigest, url); // Recommendation bytes32 hashFunctionDigest = keccak256(bytes(hashFunction)); bytes32 assetDigest = keccak256(bytes(assetBytes)); return abi.encodePacked(bytes4(hashFunctionDigest), assetDigest, url);
There are 6 instances of this issue:
<details> <summary>see instances</summary>File: contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol // Description * 2. Query the ERC725Y storage with the data key `[_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY] + <bytes32 typeId>`. // Recommendation * 2. Query the ERC725Y storage with the data key `[_LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX] + <bytes32 typeId>`.
File: contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol // Description // Generate the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY + <bytes32 typeId>} // Recommendation // Generate the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX + <bytes32 typeId>}
File: contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol // Description // Query the ERC725Y storage with the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY + <bytes32 typeId>} // Recommendation // Query the ERC725Y storage with the data key {_LSP1_UNIVERSAL_RECEIVER_DELEGATE_PREFIX + <bytes32 typeId>}
File: contracts/LSP2ERC725YJSONSchema/LSP2Utils.sol // Description /** * @dev Generate a ASSETURL valueContent * @param hashFunction The function used to hash the JSON file * @param assetBytes Bytes value of the JSON file * @param url The URL where the JSON file is hosted */ // Recommendation /** * @dev Generate an ASSETURL valueContent * @param hashFunction The function used to hash the asset file * @param assetBytes Bytes value of the asset file * @param url The URL where the asset file is hosted */
File: contracts/LSP5ReceivedAssets/LSP5Utils.sol // Description // Updating the number of the received assets (decrementing by 1 // Recommendation // Updating the number of the received assets (decrementing by 1)
</details>File: submodules/ERC725/implementations/contracts/ERC725XCore.sol // Description * see `IERC725X,execute(uint256[],address[],uint256[],bytes[])` // Recommendation * see `IERC725X.execute(uint256[],address[],uint256[],bytes[])`
There are 4 instances of this issue:
<details> <summary>see instances</summary>File: contracts/UniversalProfile.sol // Description * @dev Implementation of the ERC725Account + LSP1 universalReceiver // Recommendation * @dev Implementation of the ERC725Account + LSP1 UniversalReceiver
File: contracts/UniversalProfileInit.sol // Description * @dev Implementation of the ERC725Account + LSP1 universalReceiver // Recommendation * @dev Implementation of the ERC725Account + LSP1 UniversalReceiver
File: contracts/UniversalProfileInit.sol // Description * @notice deploying a `UniversalProfileInit` base contract to be used behind proxy // Recommendation * @notice Deploying a `UniversalProfileInit` base contract to be used behind proxy
</details>File: contracts/UniversalProfileInitAbstract.sol // Description * @dev Implementation of the ERC725Account + LSP1 universalReceiver // Recommendation * @dev Implementation of the ERC725Account + LSP1 UniversalReceiver
@inheritdoc
There are 4 instances of this issue:
<details> <summary>see instances</summary>File: contracts/LSP0ERC725Account/LSP0ERC725AccountCore.sol // Description /** * @dev Allows a caller to batch different function calls in one call. * Perform a delegatecall on self, to call different functions with preserving the context * It is not possible to send value along the functions call due to the use of delegatecall. * * @param data An array of ABI encoded function calls to be called on the contract. * @return results An array of values returned by the executed functions. */ // Recommendation /** * @inheritdoc ILSP0ERC725Account */
File: LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol // Description /** * @dev Handles two cases: * - Registers the address of received assets (exclusively LSP7 and LSP8) and vaults (exclusively LSP9) according * to {LSP5-ReceivedAssets} and {LSP10-ReceivedVaults} respectively * * - Removes the address of registered assets and vaults when the full balance is sent from the LSP0ERC725Account contract * * Requirements: * - The contract should be able to setData the LSP5 and LSP10 data Keys according to the logic of the owner * of the LSP0ERC725Account. * * - Cannot accept native tokens */ // Recommendation /** * @inheritdoc ILSP1UniversalReceiver * @dev Handles two cases: * - Registers the address of received assets (exclusively LSP7 and LSP8) and vaults (exclusively LSP9) according * to {LSP5-ReceivedAssets} and {LSP10-ReceivedVaults} respectively * * - Removes the address of registered assets and vaults when the full balance is sent from the LSP0ERC725Account contract * * Requirements: * - The contract should be able to setData the LSP5 and LSP10 data Keys according to the logic of the owner * of the LSP0ERC725Account. * * - Cannot accept native tokens */
File: LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol // Description /** * @dev Returns the name of the token. * @return The name of the token */ // Recommendation /** * @inheritdoc ILSP4Compatibility */
</details>File: LSP1UniversalReceiver/LSP1UniversalReceiverDelegateUP/LSP1UniversalReceiverDelegateUP.sol // Description /** * @dev Returns the symbol of the token, usually a shorter version of the name. * @return The symbol of the token */ // Recommendation /** * @inheritdoc ILSP4Compatibility */
Except for a few cases, it is useful to prepend to a Custom Error name its origin (e.g. module, contract). Almost all Lukso's Custom Errors are prepended with the standard name (e.g. LSP1
, LSP14
) and there is no reason why the following cases do not follow this pattern:
LSP1Errors.sol
.LSP6Errors.sol
.LSP17Errors.sol
.https://github.com/code-423n4/2023-06-lukso/blob/09bbdd68eeba6ed4dd624286c94a1947f79c1959/contracts/LSP1UniversalReceiver/LSP1Errors.sol https://github.com/code-423n4/2023-06-lukso/blob/09bbdd68eeba6ed4dd624286c94a1947f79c1959/contracts/LSP17ContractExtension/LSP17Errors.sol https://github.com/code-423n4/2023-06-lukso/blob/09bbdd68eeba6ed4dd624286c94a1947f79c1959/contracts/LSP6KeyManager/LSP6Errors.sol
#0 - c4-judge
2023-08-02T09:34:50Z
trust1995 marked the issue as grade-b