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: 6/22
Findings: 2
Award: $714.09
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: gpersoon
Also found by: DavidGiladi, MiloTruck, Rolezn, banpaleo5, catellatech, matrix_0wl, naman1778, vnavascues
421.2796 USDC - $421.28
Issue | Contexts | |
---|---|---|
LOW‑1 | Double type casts create complexity within the code | 2 |
LOW‑2 | Constructor is missing zero address check | 1 |
LOW‑3 | Assembly calldata forwarded to external contract is missing contract existence checks | 2 |
LOW‑4 | Missing Checks for Address(0x0) | 1 |
LOW‑5 | Contracts are designed to receive ETH but do not implement function for withdrawal | 1 |
LOW‑6 | Unnecessary Low level calls should be avoided | 1 |
LOW‑7 | Empty receive() /payable fallback() function does not authorize requests | 3 |
Total: 11 contexts over 7 issues
Issue | Contexts | |
---|---|---|
NC‑1 | Event emit should emit a parameter | 2 |
NC‑2 | Function creates dirty bits | 2 |
NC‑3 | Generate perfect code headers for better solidity code layout and readability | 7 |
NC‑4 | Missing event for critical parameter change | 2 |
NC‑5 | Consider moving msg.sender checks to a common authorization modifier | 9 |
NC‑6 | Non-external /public function names should begin with an underscore | 37 |
NC‑7 | override function arguments that are unused should have the variable name removed or commented out to avoid compiler warnings | 1 |
NC‑8 | Using underscore at the end of variable name | 8 |
NC‑9 | Unusual loop variable | 4 |
NC‑10 | Use named function calls | 1 |
NC‑11 | Use of _renounceOwnership can lead to unexpected behaviors | 10 |
NC‑12 | Use SMTChecker | 1 |
Total: 84 contexts over 12 issues
Double type casting should be avoided in Solidity contracts to prevent unintended consequences and ensure accurate data representation. Performing multiple type casts in succession can lead to unexpected truncation, rounding errors, or loss of precision, potentially compromising the contract's functionality and reliability. Furthermore, double type casting can make the code less readable and harder to maintain, increasing the likelihood of errors and misunderstandings during development and debugging. To ensure precise and consistent data handling, developers should use appropriate data types and avoid unnecessary or excessive type casting, promoting a more robust and dependable contract execution.
File: LSP1UniversalReceiverDelegateUP.sol 139: uint128 arrayIndex = uint128(uint160(bytes20(notifierMapValue)));
File: LSP2Utils.sol 296: if (uint224(uint256(key)) != 0) return false;
In Solidity, constructors often take address parameters to initialize important components of a contract, such as owner or linked contracts. However, without a check, there's a risk that an address parameter could be mistakenly set to the zero address (0x0). This could occur due to a mistake or oversight during contract deployment. A zero address in a crucial role can cause serious issues, as it cannot perform actions like a normal address, and any funds sent to it are irretrievable. Therefore, it's crucial to include a zero address check in constructors to prevent such potential problems. If a zero address is detected, the constructor should revert the transaction.
File: LSP7DigitalAsset.sol 39: constructor: bool isNonDivisible_
Low-level calls return success if there is no code present at the specified address. In addition to the zero-address checks, add a check to verify that extcodesize()
is non-zero.
File: LSP0ERC725AccountCore.sol 821: let success := call(
File: LSP17Extendable.sol 108: let success := call(
Lack of zero-address validation on address parameters may lead to transaction reverts, waste gas, require resubmission of transactions and may even force contract redeployments in certain cases within the protocol.
File: LSP1Utils.sol 41: function callUniversalReceiverWithCallerInfos: address universalReceiverDelegate
Consider adding explicit zero-address validation on input parameters of address type.
The following contracts can receive ETH but can not withdraw, ETH is occasionally sent by users will be stuck in those contracts. This functionality also applies to baseTokens resulting in locked tokens and loss of funds.
File: LSP0ERC725AccountCore.sol 117: receive() external payable virtual {
Provide a rescue ETH and rescueTokens function
Avoid making unnecessary low-level calls to the system whenever possible, as they function differently from contract-type calls. For instance:
Using address.call(abi.encodeWithSelector("fancy(bytes32)", mybytes)
does not confirm whether the target is genuinely a contract, whereas ContractInterface(address).fancy(mybytes)
does.
Moreover, when invoking functions declared as view
/pure
, the Solidity compiler executes a staticcall
, offering extra security guarantees that are absent in low-level calls. Additionally, return values must be manually decoded when making low-level calls.
Note: If a low-level call is required, consider using Contract.function.selector
instead of employing a hardcoded ABI string for encoding.
File: LSP20CallVerification.sol function _verifyCall( address logicVerifier ) internal virtual returns (bool verifyAfter) { (bool success, bytes memory returnedData) = logicVerifier.call( abi.encodeWithSelector( ILSP20.lsp20VerifyCall.selector, msg.sender, msg.value, msg.data ) ); _validateCall(false, success, returnedData); bytes4 magicValue = abi.decode(returnedData, (bytes4)); if (bytes3(magicValue) != bytes3(ILSP20.lsp20VerifyCall.selector)) revert LSP20InvalidMagicValue(false, returnedData); return bytes1(magicValue[3]) == 0x01 ? true : false; }
When reaching out to a known contract within the system, always opt for typed contract calls (interfaces/contracts) rather than low-level calls. This approach helps prevent mistakes, potentially unverified return values, and ensures security guarantees.
receive()
/payable fallback()
function does not authorize requestsIf the intention is for the Ether to be used, the function should call another function, otherwise it should revert (e.g. require(msg.sender == address(weth))
). Having no access control on the function means that someone may send Ether to the contract, and have no way to get anything back out, which is a loss of funds. If the concern is having to spend a small amount of gas to check the sender against an immutable address, the code should at least have a function to rescue unused Ether.
File: ERC725.sol 18: receive() external payable {}
File: ERC725Init.sol 12: receive() external payable {}
File: ERC725InitAbstract.sol 34: receive() external payable {}
Some emitted events do not have any emitted parameters. It is recommended to add some parameter such as state changes or value changes when events are emitted
File: LSP14Ownable2Step.sol 165: emit RenounceOwnershipStarted() 178: emit OwnershipRenounced()
This explanation should be added in the NatSpec comments of this function that sends ether with call;
Note that this code probably isn’t secure or a good use case for assembly because a lot of memory management and security checks are bypassed. Use with caution! Some functions in this contract knowingly create dirty bits at the destination of the free memory pointer.
File: LSP0ERC725AccountCore.sol 821: function _fallbackLSP17Extendable() internal virtual override { address extension = _getExtension(msg.sig); if (msg.sig == bytes4(0) && extension == address(0)) return; if (extension == address(0)) revert NoExtensionFoundForFunctionSelector(msg.sig); assembly { calldatacopy(0, 0, calldatasize()) mstore(calldatasize(), shl(96, caller())) mstore(add(calldatasize(), 20), callvalue()) let success := call( gas(), extension, 0, 0, add(calldatasize(), 52), 0, 0 ) returndatacopy(0, 0, returndatasize()) switch success case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } }
File: LSP17Extendable.sol 108: function _getExtension( bytes4 functionSelector ) internal view virtual returns (address); function _fallbackLSP17Extendable() internal virtual { address extension = _getExtension(msg.sig); if (extension == address(0)) revert NoExtensionFoundForFunctionSelector(msg.sig); assembly { calldatacopy(0, 0, calldatasize()) mstore(calldatasize(), shl(96, caller())) mstore(add(calldatasize(), 20), callvalue()) let success := call( gas(), extension, 0, 0, add(calldatasize(), 52), 0, 0 ) returndatacopy(0, 0, returndatasize()) switch success case 0 { revert(0, returndatasize()) } default { return(0, returndatasize()) } } } }
Add this comment to the function:
/// @dev Use with caution! Some functions in this contract knowingly create dirty bits at the destination of the free memory pointer. Note that this code probably isn’t secure or a good use case for assembly because a lot of memory management and security checks are bypassed.
It is recommended to use pre-made headers for Solidity code layout and readability: https://github.com/transmissions11/headers
/*////////////////////////////////////////////////////////////// TESTING 123 //////////////////////////////////////////////////////////////*/
File: LSP14Ownable2Step.sol 5: LSP14Ownable2Step.sol
File: LSP4Compatibility.sol 5: LSP4Compatibility.sol
File: LSP7CompatibleERC20.sol 6: LSP7CompatibleERC20.sol
File: LSP7Mintable.sol 6: LSP7Mintable.sol
File: LSP8CompatibleERC721.sol 9: LSP8CompatibleERC721.sol 43: LSP8CompatibleERC721.sol
</details>File: LSP8Mintable.sol 6: LSP8Mintable.sol
When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.
File: LSP6Utils.sol 150: function setDataViaKeyManager(
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L150
File: ERC725YCore.sol 73: function setDataBatch(
msg.sender
checks to a common authorization modifier
File: LSP0ERC725AccountCore.sol 235: if (msg.sender == _owner) { 293: if (msg.sender == _owner) { 350: if (msg.sender == _owner) { 395: if (msg.sender == _owner) { 663: if (msg.sender == _owner) {
File: LSP0ERC725AccountCore.sol 563: if (msg.sender == currentOwner) {
File: LSP6KeyManagerCore.sol 265: if (msg.sender == _target) { 309: if (msg.sender == _target) {
</details>File: OwnableUnset.sol 61: require(owner() == msg.sender, "Ownable: caller is not the owner");
external
/public
function names should begin with an underscoreAccording to the Solidity Style Guide, Non-external
/public
function names should begin with an <a href="https://docs.soliditylang.org/en/latest/style-guide.html#underscore-prefix-for-non-external-functions-and-variables">underscore</a>
File: LSP0Utils.sol 24: function getLSP1DelegateValue( mapping(bytes32 => bytes) storage erc725YStorage ) internal view returns (bytes memory) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP0ERC725Account/LSP0Utils.sol#L24
File: LSP0Utils.sol 37: function getLSP1DelegateValueForTypeId( mapping(bytes32 => bytes) storage erc725YStorage, bytes32 typeId ) internal view returns (bytes memory) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP0ERC725Account/LSP0Utils.sol#L37
File: LSP10Utils.sol 63: function generateReceivedVaultKeys( address receiver, address vault, bytes32 vaultMapKey ) internal view returns (bytes32[] memory keys, bytes[] memory values) {
File: LSP10Utils.sol 115: function generateSentVaultKeys( address sender, bytes32 vaultMapKey, uint128 vaultIndex ) internal view returns (bytes32[] memory keys, bytes[] memory values) {
File: LSP10Utils.sol 233: function getLSP10ReceivedVaultsCount( IERC725Y account ) internal view returns (bytes memory) {
File: LSP17Utils.sol 13: function isExtension( uint256 parametersLengthWithOffset, uint256 msgDataLength ) internal pure returns (bool) {
File: LSP1Utils.sol 22: function tryNotifyUniversalReceiver( address lsp1Implementation, bytes32 typeId, bytes memory data ) internal {
File: LSP1Utils.sol 40: function callUniversalReceiverWithCallerInfos( address universalReceiverDelegate, bytes32 typeId, bytes calldata receivedData, address msgSender, uint256 msgValue ) internal returns (bytes memory) {
File: LSP1Utils.sol 72: function getTransferDetails( bytes32 typeId ) internal pure returns ( bool invalid, bytes10 mapPrefix, bytes4 interfaceId, bool isReceiving ) {
File: LSP2Utils.sol 22: function generateSingletonKey( string memory keyName ) internal pure returns (bytes32) {
File: LSP2Utils.sol 32: function generateArrayKey( string memory keyName ) internal pure returns (bytes32) {
File: LSP2Utils.sol 52: function generateArrayElementKeyAtIndex( bytes32 arrayKey, uint128 index ) internal pure returns (bytes32) {
File: LSP2Utils.sol 71: function generateMappingKey( string memory firstWord, string memory lastWord ) internal pure returns (bytes32) {
File: LSP2Utils.sol 94: function generateMappingKey( string memory firstWord, address addr ) internal pure returns (bytes32) {
File: LSP2Utils.sol 115: function generateMappingKey( bytes10 keyPrefix, bytes20 bytes20Value ) internal pure returns (bytes32) {
File: LSP2Utils.sol 136: function generateMappingWithGroupingKey( string memory firstWord, string memory secondWord, address addr ) internal pure returns (bytes32) {
File: LSP2Utils.sol 161: function generateMappingWithGroupingKey( bytes6 keyPrefix, bytes4 mapPrefix, bytes20 subMapKey ) internal pure returns (bytes32) {
File: LSP2Utils.sol 181: function generateMappingWithGroupingKey( bytes10 keyPrefix, bytes20 bytes20Value ) internal pure returns (bytes32) {
File: LSP2Utils.sol 199: function generateJSONURLValue( string memory hashFunction, string memory json, string memory url ) internal pure returns (bytes memory) {
File: LSP2Utils.sol 216: function generateASSETURLValue( string memory hashFunction, string memory assetBytes, string memory url ) internal pure returns (bytes memory) {
File: LSP2Utils.sol 231: function isEncodedArray(bytes memory data) internal pure returns (bool) {
File: LSP2Utils.sol 251: function isEncodedArrayOfAddresses( bytes memory data ) internal pure returns (bool) {
File: LSP2Utils.sol 283: function isBytes4EncodedArray( bytes memory data ) internal pure returns (bool) {
File: LSP2Utils.sol 312: function isCompactBytesArray( bytes memory compactBytesArray ) internal pure returns (bool) {
File: LSP5Utils.sol 64: function generateReceivedAssetKeys( address receiver, address asset, bytes32 assetMapKey, bytes4 interfaceID ) internal view returns (bytes32[] memory keys, bytes[] memory values) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP5ReceivedAssets/LSP5Utils.sol#L64
File: LSP5Utils.sol 117: function generateSentAssetKeys( address sender, bytes32 assetMapKey, uint128 assetIndex ) internal view returns (bytes32[] memory keys, bytes[] memory values) {
File: LSP5Utils.sol 244: function getLSP5ReceivedAssetsCount( IERC725Y account ) internal view returns (bytes memory) {
File: LSP6Utils.sol 25: function getPermissionsFor( IERC725Y target, address caller ) internal view returns (bytes32) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L25
File: LSP6Utils.sol 39: function getAllowedCallsFor( IERC725Y target, address from ) internal view returns (bytes memory) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L39
File: LSP6Utils.sol 58: function getAllowedERC725YDataKeysFor( IERC725Y target, address caller ) internal view returns (bytes memory) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L58
File: LSP6Utils.sol 78: function hasPermission( bytes32 addressPermission, bytes32 permissionToCheck ) internal pure returns (bool) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L78
File: LSP6Utils.sol 91: function isCompactBytesArrayOfAllowedCalls( bytes memory allowedCallsCompacted ) internal pure returns (bool) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L91
File: LSP6Utils.sol 120: function isCompactBytesArrayOfAllowedERC725YDataKeys( bytes memory allowedERC725YDataKeysCompacted ) internal pure returns (bool) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L120
File: LSP6Utils.sol 150: function setDataViaKeyManager( address keyManagerAddress, bytes32[] memory keys, bytes[] memory values ) internal returns (bytes memory result) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L150
File: LSP6Utils.sol 169: function combinePermissions( bytes32[] memory permissions ) internal pure returns (bytes32) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L169
File: LSP6Utils.sol 194: function generateNewPermissionsKeys( IERC725Y account, address controller, bytes32 permissions ) internal view returns (bytes32[] memory keys, bytes[] memory values) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L194
File: LSP6Utils.sol 226: function getPermissionName( bytes32 permission ) internal pure returns (string memory errorMessage) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L226
</details>override
function arguments that are unused should have the variable name removed or commented out to avoid compiler warningsFile: LSP0ERC725AccountCore.sol 851: function _getExtension: bytes4 functionSelector
The use of underscore at the end of the variable name is uncommon and also suggests that the variable name was not completely changed. Consider refactoring variableName_
to variableName
.
File: LSP6KeyManager.sol 20: target_;
File: LSP6KeyManagerInitAbstract.sol 22: target_;
File: LSP7DigitalAsset.sol 45: isNonDivisible_;
File: LSP7DigitalAssetInitAbstract.sol 34: isNonDivisible_;
File: LSP7CappedSupply.sol 28: tokenSupplyCap_;
File: LSP7CappedSupplyInitAbstract.sol 28: tokenSupplyCap_;
File: LSP8CappedSupply.sol 30: tokenSupplyCap_;
</details>File: LSP8CappedSupplyInitAbstract.sol 30: tokenSupplyCap_;
The normal name for loop variables is i
, and when there is a nested loop, to use j
. The code below chooses to use the variables differently, which may lead to confusion
File: LSP2Utils.sol 251: function isEncodedArrayOfAddresses( bytes memory data ) internal pure returns (bool) { if (!isEncodedArray(data)) return false; uint256 offset = uint256(bytes32(data)); uint256 arrayLength = data.toUint256(offset); uint256 pointer = offset + 32; for (uint256 ii = 0; ii < arrayLength; ) { bytes32 key = data.toBytes32(pointer); if (bytes12(key) != bytes12(0)) return false; pointer += 32; unchecked { ++ii; } } return true; }
File: LSP2Utils.sol 283: function isBytes4EncodedArray( bytes memory data ) internal pure returns (bool) { if (!isEncodedArray(data)) return false; uint256 offset = uint256(bytes32(data)); uint256 arrayLength = data.toUint256(offset); uint256 pointer = offset + 32; for (uint256 ii = 0; ii < arrayLength; ) { bytes32 key = data.toBytes32(pointer); if (uint224(uint256(key)) != 0) return false; pointer += 32; unchecked { ++ii; } } return true; }
File: LSP6ExecuteModule.sol 245: function _verifyAllowedCall( address controlledContract, address controllerAddress, bytes calldata payload ) internal view virtual { ( uint256 operationType, address to, uint256 value, bytes4 selector, bool isEmptyCall ) = _extractExecuteParameters(payload); bytes memory allowedCalls = ERC725Y(controlledContract) .getAllowedCallsFor(controllerAddress); if (allowedCalls.length == 0) { revert NoCallsAllowed(controllerAddress); } bytes4 requiredCallTypes = _extractCallType( operationType, value, isEmptyCall ); for (uint256 ii = 0; ii < allowedCalls.length; ii += 34) { if (ii + 34 > allowedCalls.length) { revert InvalidEncodedAllowedCalls(allowedCalls); } bytes memory allowedCall = BytesLib.slice(allowedCalls, ii + 2, 32); if ( bytes28(bytes32(allowedCall) << 32) == bytes28(type(uint224).max) ) { revert InvalidWhitelistedCall(controllerAddress); } if ( _isAllowedCallType(allowedCall, requiredCallTypes) && _isAllowedAddress(allowedCall, to) && _isAllowedStandard(allowedCall, to) && _isAllowedFunction(allowedCall, selector) ) return; } revert NotAllowedCall(controllerAddress, to, selector); }
File: LSP8IdentifiableDigitalAssetCore.sol 258: function _clearOperators( address tokenOwner, bytes32 tokenId ) internal virtual { EnumerableSet.AddressSet storage operatorsForTokenId = _operators[ tokenId ]; uint256 operatorListLength = operatorsForTokenId.length(); for (uint256 i = 0; i < operatorListLength; ) { address operator = operatorsForTokenId.at(0); _revokeOperator(operator, tokenOwner, tokenId); unchecked { ++i; } } }
Code base has an extensive use of named function calls, but it somehow missed one instance where this would be appropriate.
It should use named function calls on function call, as such:
library.exampleFunction{value: _data.amount.value}({ _id: _data.id, _amount: _data.amount.value, _token: _data.token, _example: "", _metadata: _data.metadata });
File: ERC725XCore.sol 186: (bool success, bytes memory returnData) = target.call{value: value}( data );
_renounceOwnership
can lead to unexpected behaviorsRenouncing to the ownership of a contract can lead to unexpected behaviors. Method setAddresses
can only be called once therefore, due to the call from the method to _renounceOwnership
. Consider using a method to submit a proposal of new addresses, then another one to accept the proposal where _renounceOwnership
is called at the end.
File: LSP0ERC725AccountCore.sol 664: return LSP14Ownable2Step._renounceOwnership(); 672: LSP14Ownable2Step._renounceOwnership();
File: LSP14Ownable2Step.sol 112: _renounceOwnership();
File: LSP14Ownable2Step.sol 130: delete _renounceOwnershipStartedAt; 177: delete _renounceOwnershipStartedAt;
File: LSP14Ownable2Step.sol 150: function _renounceOwnership() internal virtual { 152: uint256 confirmationPeriodStart = _renounceOwnershipStartedAt + 158: _renounceOwnershipStartedAt == 0 161: _renounceOwnershipStartedAt == 0 163: _renounceOwnershipStartedAt = currentBlock;
The highest tier of smart contract behavior assurance is formal mathematical verification. All assertions that are made are guaranteed to be true across all inputs → The quality of your asserts is the quality of your verification
https://twitter.com/0xOwenThurm/status/1614359896350425088?t=dbG9gHFigBX85Rv29lOjIQ&s=19
#0 - c4-judge
2023-08-02T09:46:17Z
trust1995 marked the issue as grade-a
🌟 Selected for report: Raihan
Also found by: DavidGiladi, LeoS, Rageur, ReyAdmirado, Rolezn, SAAJ, SAQ, SM3_SS, Sathish9098, hunter_w3b, matrix_0wl, naman1778, petrichor
292.8073 USDC - $292.81
Issue | Contexts | Estimated Gas Saved | |
---|---|---|---|
GAS‑1 | abi.encode() is less efficient than abi.encodepacked() | 3 | 159 |
GAS‑2 | Consider activating via-ir for deploying | 1 | 250 |
GAS‑3 | Use assembly to emit events | 48 | 1824 |
GAS‑4 | Counting down in for statements is more gas efficient | 14 | 3598 |
GAS‑5 | Using delete statement can save gas | 6 | 48 |
GAS‑6 | Use assembly to write address storage values | 14 | 1036 |
GAS‑7 | The result of a function call should be cached rather than re-calling the function | 25 | 1250 |
GAS‑8 | Help The Optimizer By Saving A Storage Variable's Reference Instead Of Repeatedly Fetching It | 13 | 702 |
GAS‑9 | Usage of uints /ints smaller than 32 bytes (256 bits) incurs overhead | 15 | 90 |
GAS‑10 | Use nested if and avoid multiple check combinations | 14 | 84 |
GAS‑11 | Using Openzeppelin Ownable2Step.sol is more gas efficient | 4 | 32 |
GAS‑12 | Using XOR (^) and AND (&) bitwise equivalents | 161 | 2093 |
Total: 326 contexts over 12 issues
abi.encode()
is less efficient than abi.encodepacked()
See for more information: https://github.com/ConnorBlockchain/Solidity-Encode-Gas-Comparison
File: LSP0ERC725AccountCore.sol 253: LSP20CallVerification._verifyCallResult(_owner, abi.encode(result));
File: LSP0ERC725AccountCore.sol 319: abi.encode(results)
File: LSP0ERC725AccountCore.sol 528: returnedValues = abi.encode(
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(); c1.optimized(); } } contract Contract0 { string a = "Code4rena"; function not_optimized() public returns(bytes32){ return keccak256(abi.encode(a)); } } contract Contract1 { string a = "Code4rena"; function optimized() public returns(bytes32){ return keccak256(abi.encodePacked(a)); } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
101871 | 683 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 2661 | 2661 | 2661 | 2661 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
99465 | 671 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 2608 | 2608 | 2608 | 2608 | 1 |
via-ir
for deployingThe IR-based code generator was introduced with an aim to not only allow code generation to be more transparent and auditable but also to enable more powerful optimization passes that span across functions.
You can enable it on the command line using --via-ir
or with the option {"viaIR": true}
.
This will take longer to compile, but you can just simple test it before deploying and if you got a better benchmark then you can add --via-ir to your deploy command
More on: https://docs.soliditylang.org/en/v0.8.17/ir-breaking-changes.html
We can use assembly to emit events efficiently by utilizing scratch space
and the free memory pointer
. This will allow us to potentially avoid memory expansion costs.
Note: In order to do this optimization safely, we will need to cache and restore the free memory pointer.
For example, for a generic emit
event for eventSentAmountExample
:
// uint256 id, uint256 value, uint256 amount emit eventSentAmountExample(id, value, amount);
We can use the following assembly emit events:
assembly { let memptr := mload(0x40) mstore(0x00, calldataload(0x44)) mstore(0x20, calldataload(0xa4)) mstore(0x40, amount) log1( 0x00, 0x60, // keccak256("eventSentAmountExample(uint256,uint256,uint256)") 0xa622cf392588fbf2cd020ff96b2f4ebd9c76d7a4bc7f3e6b2f18012312e76bc3 ) mstore(0x40, memptr) }
File: LSP0ERC725AccountCore.sol 229: emit ValueReceived(msg.sender, msg.value);
File: LSP0ERC725AccountCore.sol 287: emit ValueReceived(msg.sender, msg.value);
File: LSP0ERC725AccountCore.sol 344: emit ValueReceived(msg.sender, msg.value);
File: LSP0ERC725AccountCore.sol 385: emit ValueReceived(msg.sender, msg.value);
File: LSP0ERC725AccountCore.sol 465: emit ValueReceived(msg.sender, msg.value);
File: LSP0ERC725AccountCore.sol 566: emit OwnershipTransferStarted(currentOwner, pendingNewOwner); 566: emit OwnershipTransferStarted(currentOwner, pendingNewOwner);
File: LSP0ERC725AccountInitAbstract.sol 63: emit ValueReceived(msg.sender, msg.value);
File: LSP14Ownable2Step.sol 72: emit OwnershipTransferStarted(currentOwner, newOwner);
File: LSP14Ownable2Step.sol 165: emit RenounceOwnershipStarted(); 178: emit OwnershipRenounced();
File: LSP6KeyManagerCore.sol 269: emit VerifiedCall(caller, msgValue, bytes4(data));
File: LSP6KeyManagerCore.sol 334: emit VerifiedCall(msg.sender, msgValue, bytes4(payload));
File: LSP6KeyManagerCore.sol 400: emit VerifiedCall(signer, msgValue, bytes4(payload));
File: LSP7DigitalAssetCore.sol 279: emit AuthorizedOperator(operator, tokenOwner, amount); 281: emit RevokedOperator(operator, tokenOwner);
File: LSP7DigitalAssetCore.sol 375: emit Transfer(operator, from, address(0), amount, false, data);
File: LSP7DigitalAssetCore.sol 422: emit Transfer(operator, from, to, amount, allowNonLSP1Recipient, data);
File: LSP7CompatibleERC20.sol 113: emit Approval(tokenOwner, operator, amount);
File: LSP7CompatibleERC20.sol 123: emit Transfer(from, to, amount);
File: LSP7CompatibleERC20.sol 133: emit Transfer(address(0), to, amount);
File: LSP7CompatibleERC20.sol 142: emit Transfer(from, address(0), amount);
File: LSP7CompatibleERC20InitAbstract.sol 121: emit Approval(tokenOwner, operator, amount);
File: LSP7CompatibleERC20InitAbstract.sol 131: emit Transfer(from, to, amount);
File: LSP7CompatibleERC20InitAbstract.sol 141: emit Transfer(address(0), to, amount);
File: LSP7CompatibleERC20InitAbstract.sol 150: emit Transfer(from, address(0), amount);
File: LSP8IdentifiableDigitalAssetCore.sol 126: emit AuthorizedOperator(operator, tokenOwner, tokenId);
File: LSP8IdentifiableDigitalAssetCore.sol 252: emit RevokedOperator(operator, tokenOwner, tokenId);
File: LSP8IdentifiableDigitalAssetCore.sol 373: emit Transfer(operator, tokenOwner, address(0), tokenId, false, data);
File: LSP8IdentifiableDigitalAssetCore.sol 424: emit Transfer(operator, from, to, tokenId, allowNonLSP1Recipient, data);
File: LSP8CompatibleERC721.sol 157: emit Approval(tokenOwnerOf(bytes32(tokenId)), operator, tokenId);
File: LSP8CompatibleERC721.sol 223: emit Approval(tokenOwnerOf(tokenId), operator, uint256(tokenId));
File: LSP8CompatibleERC721.sol 242: emit Transfer(from, to, uint256(tokenId));
File: LSP8CompatibleERC721.sol 265: emit Transfer(address(0), to, uint256(tokenId));
File: LSP8CompatibleERC721.sol 275: emit Transfer(tokenOwner, address(0), uint256(tokenId));
File: LSP8CompatibleERC721.sol 294: emit ApprovalForAll(tokensOwner, operator, approved);
File: LSP8CompatibleERC721InitAbstract.sol 157: emit Approval(tokenOwnerOf(bytes32(tokenId)), operator, tokenId);
File: LSP8CompatibleERC721InitAbstract.sol 223: emit Approval(tokenOwnerOf(tokenId), operator, uint256(tokenId));
File: LSP8CompatibleERC721InitAbstract.sol 242: emit Transfer(from, to, uint256(tokenId));
File: LSP8CompatibleERC721InitAbstract.sol 265: emit Transfer(address(0), to, uint256(tokenId));
File: LSP8CompatibleERC721InitAbstract.sol 275: emit Transfer(tokenOwner, address(0), uint256(tokenId));
File: LSP8CompatibleERC721InitAbstract.sol 294: emit ApprovalForAll(tokensOwner, operator, approved);
File: ERC725XCore.sol 183: emit Executed(OPERATION_0_CALL, target, value, bytes4(data));
File: ERC725XCore.sol 207: emit Executed(OPERATION_3_STATICCALL, target, 0, bytes4(data));
File: ERC725XCore.sol 229: emit Executed(OPERATION_4_DELEGATECALL, target, 0, bytes4(data));
File: ERC725XCore.sol 307: emit ContractCreated(OPERATION_2_CREATE2, contractAddress, value, salt);
File: ERC725YCore.sol 109: emit DataChanged(dataKey, dataValue);
</details>File: OwnableUnset.sol 72: emit OwnershipTransferred(oldOwner, newOwner);
for
statements is more gas efficientCounting down is more gas efficient than counting up because neither we are making zero variable to non-zero variable and also we will get gas refund in the last transaction when making non-zero to zero variable.
File: LSP0ERC725AccountCore.sol 175: for (uint256 i; i < data.length; ) {
File: LSP0ERC725AccountCore.sol 396: for (uint256 i = 0; i < dataKeys.length; ) {
File: LSP2Utils.sol 261: for (uint256 ii = 0; ii < arrayLength; ) {
File: LSP2Utils.sol 292: for (uint256 ii = 0; ii < arrayLength; ) {
File: LSP6KeyManagerCore.sol 163: for (uint256 ii = 0; ii < payloads.length; ) {
File: LSP6KeyManagerCore.sol 223: for (uint256 ii = 0; ii < payloads.length; ) {
File: LSP6Utils.sol 173: for (uint256 i = 0; i < permissions.length; i++) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L173
File: LSP6ExecuteModule.sol 272: for (uint256 ii = 0; ii < allowedCalls.length; ii += 34) {
File: LSP7DigitalAssetCore.sol 171: for (uint256 i = 0; i < fromLength; ) {
File: LSP8IdentifiableDigitalAssetCore.sol 227: for (uint256 i = 0; i < fromLength; ) {
File: LSP8IdentifiableDigitalAssetCore.sol 273: for (uint256 i = 0; i < operatorListLength; ) {
File: ERC725XCore.sol 150: for (uint256 i = 0; i < operationsType.length; ) {
File: ERC725YCore.sol 47: for (uint256 i = 0; i < dataKeys.length; ) {
</details>File: ERC725YCore.sol 88: for (uint256 i = 0; i < dataKeys.length; ) {
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.AddNum(); c1.AddNum(); } } contract Contract0 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=0;i<=9;i++){ _num = _num +1; } num = _num; } } contract Contract1 { uint256 num = 3; function AddNum() public { uint256 _num = num; for(uint i=9;i>=0;i--){ _num = _num +1; } num = _num; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
77011 | 311 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 7040 | 7040 | 7040 | 7040 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
73811 | 295 | ||||
Function Name | min | avg | median | max | # calls |
AddNum | 3819 | 3819 | 3819 | 3819 | 1 |
delete
statement can save gasFile: LSP2Utils.sol 328: uint256 pointer = 0;
File: LSP6Utils.sol 94: uint256 pointer = 0;
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L94
File: LSP6Utils.sol 123: uint256 pointer = 0;
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L123
File: LSP6Utils.sol 172: uint256 result = 0;
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L172
</details>File: LSP6SetDataModule.sol 129: uint256 inputDataKeysAllowed = 0; 133: uint256 ii = 0;
assembly
to write address storage valuesFile: LSP14Ownable2Step.sol 129: _pendingOwner = newOwner;
File: LSP14Ownable2Step.sol 163: _renounceOwnershipStartedAt = currentBlock;
File: LSP6KeyManager.sol 20: _target = target_;
File: LSP6KeyManagerCore.sol 519: _reentrancyStatus = false;
File: LSP6KeyManagerCore.sol 553: _reentrancyStatus = false;
File: LSP6KeyManagerCore.sol 541: _reentrancyStatus = true;
File: LSP6KeyManagerInitAbstract.sol 22: _target = target_;
File: LSP7DigitalAsset.sol 45: _isNonDivisible = isNonDivisible_;
File: LSP7DigitalAssetInitAbstract.sol 34: _isNonDivisible = isNonDivisible_;
File: LSP7CappedSupply.sol 28: _tokenSupplyCap = tokenSupplyCap_;
File: LSP7CappedSupplyInitAbstract.sol 28: _tokenSupplyCap = tokenSupplyCap_;
File: LSP8CappedSupply.sol 30: _tokenSupplyCap = tokenSupplyCap_;
File: LSP8CappedSupplyInitAbstract.sol 30: _tokenSupplyCap = tokenSupplyCap_;
</details>File: OwnableUnset.sol 71: _owner = newOwner;
External calls are expensive. Results of external function calls should be cached rather than call them multiple times. Consider caching the following:
File: LSP0ERC725AccountCore.sol 560: address currentOwner = owner(); 577: currentOwner == owner(), 598: currentOwner == owner(),
File: LSP0ERC725AccountCore.sol 664: return LSP14Ownable2Step._renounceOwnership(); 672: LSP14Ownable2Step._renounceOwnership();
File: LSP0ERC725AccountCore.sol 811: calldatacopy(0, 0, calldatasize()) 815: mstore(calldatasize(), shl(96, caller())) 818: mstore(add(calldatasize(), 20), callvalue()) 826: add(calldatasize(), 52), 832: returndatacopy(0, 0, returndatasize()) 837: revert(0, returndatasize()) 840: return(0, returndatasize())
File: LSP14Ownable2Step.sol 71: address currentOwner = owner(); 79: currentOwner == owner(),
File: LSP17Extendable.sol 98: calldatacopy(0, 0, calldatasize()) 102: mstore(calldatasize(), shl(96, caller())) 105: mstore(add(calldatasize(), 20), callvalue()) 113: add(calldatasize(), 52), 119: returndatacopy(0, 0, returndatasize()) 124: revert(0, returndatasize()) 127: return(0, returndatasize())
File: LSP8Enumerable.sol 37: uint256 index = totalSupply(); 41: uint256 lastIndex = totalSupply() - 1;
</details>File: LSP8EnumerableInitAbstract.sol 39: uint256 index = totalSupply(); 43: uint256 lastIndex = totalSupply() - 1;
To help the optimizer, declare a storage type variable and use it instead of repeatedly fetching the reference in a map or an array. The effect can be quite significant. As an example, instead of repeatedly calling someMap[someIndex], save its reference like this: SomeStruct storage someStruct = someMap[someIndex] and use it.
File: LSP6KeyManagerCore.sol 164: if ((totalValues += values[ii]) > msg.value) {
File: LSP6KeyManagerCore.sol 168: results[ii] = _execute(values[ii], payloads[ii]);
File: LSP6KeyManagerCore.sol 224: if ((totalValues += values[ii]) > msg.value) {
File: LSP6KeyManagerCore.sol 229: signatures[ii],
File: LSP6KeyManagerCore.sol 230: nonces[ii],
File: LSP6KeyManagerCore.sol 231: validityTimestamps[ii],
File: LSP6KeyManagerCore.sol 232: values[ii],
File: LSP6KeyManagerCore.sol 233: payloads[ii]
File: LSP6SetDataModule.sol 667: allowedERC725YDataKeysCompacted[pointer],
File: LSP6SetDataModule.sol 729: if (validatedInputKeysList[ii]) {
File: LSP6SetDataModule.sol 737: if ((inputDataKeys[ii] & mask) == allowedKey) {
File: LSP6SetDataModule.sol 763: if (!validatedInputKeysList[jj]) {
File: LSP6SetDataModule.sol 766: inputDataKeys[jj]
uints
/ints
smaller than 32 bytes (256 bits) incurs overheadWhen using elements that are smaller than 32 bytes, your contract's gas usage may be higher. This is because the EVM operates on 32 bytes at a time. Therefore, if the element is smaller than that, the EVM must use more operations in order to reduce the size of the element from 32 bytes to the desired size.
https://docs.soliditylang.org/en/v0.8.11/internals/layout_in_storage.html
Each operation involving a uint8
costs an extra 22-28 gas (depending on whether the other operand is also a variable of type uint8
) as compared to ones involving uint256
, due to the compiler having to clear the higher bits of the memory word before operating on the uint8
, as well as the associated stack operations of doing so. Use a larger size then downcast where needed
File: LSP10Utils.sol 83: uint128 oldArrayLength = uint128(bytes16(encodedArrayLength));
File: LSP10Utils.sol 89: uint128 newArrayLength = oldArrayLength + 1;
File: LSP10Utils.sol 133: uint128 oldArrayLength = uint128(bytes16(lsp10VaultsCountValue));
File: LSP10Utils.sol 140: uint128 newArrayLength = oldArrayLength - 1;
File: LSP1Utils.sol 89: interfaceId = _INTERFACEID_LSP7;
File: LSP1Utils.sol 96: interfaceId = _INTERFACEID_LSP8;
File: LSP1Utils.sol 103: interfaceId = _INTERFACEID_LSP9;
File: LSP1UniversalReceiverDelegateUP.sol 139: uint128 arrayIndex = uint128(uint160(bytes20(notifierMapValue)));
File: LSP5Utils.sol 85: uint128 oldArrayLength = uint128(bytes16(encodedArrayLength));
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP5ReceivedAssets/LSP5Utils.sol#L85
File: LSP5Utils.sol 134: uint128 oldArrayLength = uint128(bytes16(lsp5ReceivedAssetsCountValue));
File: LSP5Utils.sol 137: uint128 newArrayLength = oldArrayLength - 1;
File: LSP6KeyManagerCore.sol 387: uint128 startingTimestamp = uint128(validityTimestamps >> 128);
File: LSP6KeyManagerCore.sol 388: uint128 endingTimestamp = uint128(validityTimestamps);
File: LSP6Utils.sol 205: uint128 newArrayLength = arrayLength + 1;
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L205
</details>File: LSP6SetDataModule.sol 346: uint128 newLength = uint128(bytes16(inputDataValue));
if
and avoid multiple check combinationsUsing nested if
, is cheaper than using &&
multiple check combinations. There are more advantages, such as easier to read code and better coverage reports.
File: LSP0ERC725AccountCore.sol 801: if (msg.sig == bytes4(0) && extension == address(0)) return;
File: LSP10Utils.sol 76: if (encodedArrayLength.length != 0 && encodedArrayLength.length != 16) {
File: LSP1UniversalReceiverDelegateUP.sol 227: if (dataKeys.length == 0 && dataValues.length == 0)
File: LSP5Utils.sol 78: if (encodedArrayLength.length != 0 && encodedArrayLength.length != 16) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP5ReceivedAssets/LSP5Utils.sol#L78
File: LSP6KeyManagerCore.sol 338: if (!isReentrantCall && !isSetData) {
File: LSP6KeyManagerCore.sol 404: if (!isReentrantCall && !isSetData) {
File: LSP6ExecuteModule.sol 165: if (isFundingContract && !hasSuperTransferValue) {
File: LSP6ExecuteModule.sol 214: if (isTransferringValue && !hasSuperTransferValue) { 224: if (!hasSuperCall && !isCallDataPresent && !isTransferringValue) { 228: if (isCallDataPresent && !hasSuperCall) { 233: if (hasSuperCall && !isTransferringValue) return; 236: if (hasSuperTransferValue && !isCallDataPresent && isTransferringValue) 240: if (hasSuperCall && hasSuperTransferValue) return;
</details>File: LSP6SetDataModule.sol 362: if (inputDataValue.length != 0 && inputDataValue.length != 20) {
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.checkAge(19); c1.checkAgeOptimized(19); } } contract Contract0 { function checkAge(uint8 _age) public returns(string memory){ if(_age>18 && _age<22){ return "Eligible"; } } } contract Contract1 { function checkAgeOptimized(uint8 _age) public returns(string memory){ if(_age>18){ if(_age<22){ return "Eligible"; } } } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
76923 | 416 | ||||
Function Name | min | avg | median | max | # calls |
checkAge | 651 | 651 | 651 | 651 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
76323 | 413 | ||||
Function Name | min | avg | median | max | # calls |
checkAgeOptimized | 645 | 645 | 645 | 645 | 1 |
Ownable2Step.sol
is more gas efficientThe project makes secure Owner changes using the following function:
File: LSP0ERC725AccountCore.sol 624: function acceptOwnership() public virtual override { address previousOwner = owner(); _acceptOwnership(); previousOwner.tryNotifyUniversalReceiver( _TYPEID_LSP0_OwnershipTransferred_SenderNotification, "" ); msg.sender.tryNotifyUniversalReceiver( _TYPEID_LSP0_OwnershipTransferred_RecipientNotification, "" ); }
File: LSP14Ownable2Step.sol 87: function acceptOwnership() public virtual { address previousOwner = owner(); _acceptOwnership(); previousOwner.tryNotifyUniversalReceiver( _TYPEID_LSP14_OwnershipTransferred_SenderNotification, "" ); msg.sender.tryNotifyUniversalReceiver( _TYPEID_LSP14_OwnershipTransferred_RecipientNotification, "" ); }
File: LSP14Ownable2Step.sol 136: function _acceptOwnership() internal virtual { require( msg.sender == pendingOwner(), "LSP14: caller is not the pendingOwner" ); _setOwner(msg.sender); delete _pendingOwner; }
However, it is recommended to use the more gas-optimized Openzeppelin project of Ownable2Step.sol
as it is much more gas-efficient.
function acceptOwnership() public virtual { address sender = _msgSender(); require(pendingOwner() == sender, "Ownable2Step: caller is not the new owner"); _transferOwnership(sender); }
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/Ownable2Step.sol
Given 4 variables a, b, c and d represented as such:
0 0 0 0 0 1 1 0 <- a 0 1 1 0 0 1 1 0 <- b 0 0 0 0 0 0 0 0 <- c 1 1 1 1 1 1 1 1 <- d
To have a == b means that every 0 and 1 match on both variables. Meaning that a XOR (operator ^) would evaluate to 0 ((a ^ b) == 0), as it excludes by definition any equalities.Now, if a != b, this means that there’s at least somewhere a 1 and a 0 not matching between a and b, making (a ^ b) != 0.Both formulas are logically equivalent and using the XOR bitwise operator costs actually the same amount of gas.However, it is much cheaper to use the bitwise OR operator (|) than comparing the truthy or falsy values.These are logically equivalent too, as the OR bitwise operator (|) would result in a 1 somewhere if any value is not 0 between the XOR (^) statements, meaning if any XOR (^) statement verifies that its arguments are different.
File: LSP0ERC725AccountCore.sol 235: if (msg.sender == _owner) {
File: LSP0ERC725AccountCore.sol 293: if (msg.sender == _owner) {
File: LSP0ERC725AccountCore.sol 350: if (msg.sender == _owner) {
File: LSP0ERC725AccountCore.sol 395: if (msg.sender == _owner) {
File: LSP0ERC725AccountCore.sol 563: if (msg.sender == currentOwner) { 577: currentOwner == owner(),
File: LSP0ERC725AccountCore.sol 663: if (msg.sender == _owner) {
File: LSP0ERC725AccountCore.sol 702: interfaceId == _INTERFACEID_ERC1271 || 703: interfaceId == _INTERFACEID_LSP0 || 704: interfaceId == _INTERFACEID_LSP1 || 705: interfaceId == _INTERFACEID_LSP14 || 706: interfaceId == _INTERFACEID_LSP20_CALL_VERIFICATION ||
File: LSP0ERC725AccountCore.sol 751: result.length == 32 && 770: recoveredAddress == _owner
File: LSP0ERC725AccountCore.sol 801: if (msg.sig == bytes4(0) && extension == address(0)) return; 804: if (extension == address(0))
File: LSP10Utils.sol 85: if (oldArrayLength == type(uint128).max) {
File: LSP10Utils.sol 149: if (vaultIndex == newArrayLength) {
File: LSP14Ownable2Step.sol 79: currentOwner == owner(),
File: LSP14Ownable2Step.sol 127: if (newOwner == address(this)) revert CannotTransferOwnershipToSelf();
File: LSP14Ownable2Step.sol 138: msg.sender == pendingOwner(),
File: LSP14Ownable2Step.sol 158: _renounceOwnershipStartedAt == 0
File: LSP17Extendable.sol 32: interfaceId == _INTERFACEID_LSP17_EXTENDABLE ||
File: LSP17Extendable.sol 50: if (erc165Extension == address(0)) return false;
File: LSP17Extendable.sol 91: if (extension == address(0))
File: LSP17Extension.sol 24: interfaceId == _INTERFACEID_LSP17_EXTENSION ||
File: LSP1Utils.sol 85: typeId == _TYPEID_LSP7_TOKENSSENDER || 86: typeId == _TYPEID_LSP7_TOKENSRECIPIENT 90: isReceiving = typeId == _TYPEID_LSP7_TOKENSRECIPIENT ? true : false; 92: typeId == _TYPEID_LSP8_TOKENSSENDER || 93: typeId == _TYPEID_LSP8_TOKENSRECIPIENT 97: isReceiving = typeId == _TYPEID_LSP8_TOKENSRECIPIENT ? true : false; 99: typeId == _TYPEID_LSP9_OwnershipTransferred_SenderNotification || 100: typeId == _TYPEID_LSP9_OwnershipTransferred_RecipientNotification
File: LSP1UniversalReceiverDelegateUP.sol 103: if (notifier == tx.origin) revert CannotRegisterEOAsAsAssets(notifier);
File: LSP1UniversalReceiverDelegateUP.sol 170: if (balance == 0) return "LSP1: balance not updated";
File: LSP1UniversalReceiverDelegateUP.sol 227: if (dataKeys.length == 0 && dataValues.length == 0)
File: LSP1UniversalReceiverDelegateUP.sol 263: interfaceId == _INTERFACEID_LSP1 ||
File: LSP2Utils.sol 346: if (pointer == compactBytesArray.length) return true;
File: LSP4DigitalAssetMetadata.sol 56: if (dataKey == _LSP4_TOKEN_NAME_KEY) { 58: } else if (dataKey == _LSP4_TOKEN_SYMBOL_KEY) {
File: LSP4DigitalAssetMetadataInitAbstract.sol 54: if (dataKey == _LSP4_TOKEN_NAME_KEY) { 56: } else if (dataKey == _LSP4_TOKEN_SYMBOL_KEY) {
File: LSP5Utils.sol 87: if (oldArrayLength == type(uint128).max) {
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP5ReceivedAssets/LSP5Utils.sol#L87
File: LSP5Utils.sol 146: if (assetIndex == newArrayLength) {
File: LSP6KeyManagerCore.sol 98: interfaceId == _INTERFACEID_LSP6 || 99: interfaceId == _INTERFACEID_ERC1271 || 100: interfaceId == _INTERFACEID_LSP20_CALL_VERIFIER ||
File: LSP6KeyManagerCore.sol 265: if (msg.sender == _target) { 273: isSetData || isReentrantCall
File: LSP6KeyManagerCore.sol 309: if (msg.sender == _target) {
File: LSP6KeyManagerCore.sol 463: if (permissions == bytes32(0)) revert NoPermissionsSet(from); 468: if (erc725Function == IERC725Y.setData.selector) { 484: } else if (erc725Function == IERC725Y.setDataBatch.selector) { 498: } else if (erc725Function == IERC725X.execute.selector) { 506: erc725Function == ILSP14Ownable2Step.transferOwnership.selector || 507: erc725Function == ILSP14Ownable2Step.acceptOwnership.selector
File: LSP6KeyManagerInitAbstract.sol 21: if (target_ == address(0)) revert InvalidLSP6Target();
File: LSP6Utils.sol 110: if (pointer == allowedCallsCompacted.length) return true;
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L110
File: LSP6Utils.sol 137: if (elementLength == 0 || elementLength > 32) return false; 140: if (pointer == allowedERC725YDataKeysCompacted.length) return true;
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L137
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L140
File: LSP6Utils.sol 229: if (permission == _PERMISSION_CHANGEOWNER) return "TRANSFEROWNERSHIP"; 230: if (permission == _PERMISSION_EDITPERMISSIONS) return "EDITPERMISSIONS"; 231: if (permission == _PERMISSION_ADDCONTROLLER) return "ADDCONTROLLER"; 232: if (permission == _PERMISSION_ADDEXTENSIONS) return "ADDEXTENSIONS"; 233: if (permission == _PERMISSION_CHANGEEXTENSIONS) 235: if (permission == _PERMISSION_ADDUNIVERSALRECEIVERDELEGATE) 237: if (permission == _PERMISSION_CHANGEUNIVERSALRECEIVERDELEGATE) 239: if (permission == _PERMISSION_REENTRANCY) return "REENTRANCY"; 240: if (permission == _PERMISSION_SETDATA) return "SETDATA"; 241: if (permission == _PERMISSION_CALL) return "CALL"; 242: if (permission == _PERMISSION_STATICCALL) return "STATICCALL"; 243: if (permission == _PERMISSION_DELEGATECALL) return "DELEGATECALL"; 244: if (permission == _PERMISSION_DEPLOY) return "DEPLOY"; 245: if (permission == _PERMISSION_TRANSFERVALUE) return "TRANSFERVALUE"; 246: if (permission == _PERMISSION_SIGN) return "SIGN";
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L229
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L230
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L231
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L232
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L233
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L235
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L237
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L239
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L240
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L241
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L242
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L243
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L244
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L245
https://github.com/code-423n4/2023-06-lukso/tree/main/contracts/LSP6KeyManager/LSP6Utils.sol#L246
File: LSP6ExecuteModule.sol 86: if (to == address(this)) { 107: if (operationType == OPERATION_0_CALL) { 119: operationType == OPERATION_1_CREATE || 120: operationType == OPERATION_2_CREATE2 137: if (operationType == OPERATION_3_STATICCALL) { 148: if (operationType == OPERATION_4_DELEGATECALL) {
File: LSP6ExecuteModule.sol 262: if (allowedCalls.length == 0) {
File: LSP6ExecuteModule.sol 329: if (operationType == OPERATION_0_CALL) { 331: } else if (operationType == OPERATION_3_STATICCALL) { 333: } else if (operationType == OPERATION_4_DELEGATECALL) {
File: LSP6ExecuteModule.sol 362: executeCalldata.length == 164
File: LSP6ExecuteModule.sol 378: allowedAddress == address(bytes20(type(uint160).max)) || 379: to == allowedAddress;
File: LSP6ExecuteModule.sol 395: allowedStandard == bytes4(type(uint32).max) ||
File: LSP6ExecuteModule.sol 414: allowedFunction == bytes4(type(uint32).max) || 415: (isFunctionCall && (requiredFunction == allowedFunction));
File: LSP6ExecuteModule.sol 430: return (allowedCallType & requiredCallTypes == requiredCallTypes);
File: LSP6SetDataModule.sol 142: if (requiredPermission == _PERMISSION_SETDATA) { 97: if (requiredPermission == bytes32(0)) return;
File: LSP6SetDataModule.sol 142: if (requiredPermission == _PERMISSION_SETDATA) {
File: LSP6SetDataModule.sol 282: inputDataKey == _LSP1_UNIVERSAL_RECEIVER_DELEGATE_KEY ||
File: LSP6SetDataModule.sol 341: if (inputDataKey == _LSP6KEY_ADDRESSPERMISSIONS_ARRAY) { 374: ERC725Y(controlledContract).getData(inputDataKey).length == 0
File: LSP6SetDataModule.sol 426: ERC725Y(controlledContract).getData(dataKey).length == 0
File: LSP6SetDataModule.sol 461: ERC725Y(controlledContract).getData(dataKey).length == 0
File: LSP6SetDataModule.sol 479: ERC725Y(controlledContract).getData(lsp1DelegateDataKey).length == 0
File: LSP6SetDataModule.sol 512: if (allowedERC725YDataKeysCompacted.length == 0)
File: LSP6SetDataModule.sol 629: if (allowedERC725YDataKeysCompacted.length == 0) 747: if (allowedDataKeysFound == inputKeysLength) return;
File: LSP7DigitalAsset.sol 55: interfaceId == _INTERFACEID_LSP7 ||
File: LSP7DigitalAssetCore.sol 112: if (tokenOwner == operator) {
File: LSP7DigitalAssetCore.sol 131: if (from == to) revert LSP7CannotSendToSelf();
File: LSP7DigitalAssetCore.sol 268: if (operator == address(0)) { 272: if (operator == tokenOwner) {
File: LSP7DigitalAssetCore.sol 300: if (to == address(0)) {
File: LSP7DigitalAssetCore.sol 343: if (from == address(0)) {
File: LSP7DigitalAssetCore.sol 406: if (from == address(0) || to == address(0)) {
File: LSP7DigitalAssetInitAbstract.sol 49: interfaceId == _INTERFACEID_LSP7 ||
File: LSP7CappedSupplyInitAbstract.sol 24: if (tokenSupplyCap_ == 0) {
File: LSP8IdentifiableDigitalAsset.sol 49: interfaceId == _INTERFACEID_LSP8 ||
File: LSP8IdentifiableDigitalAssetCore.sol 84: if (tokenOwner == address(0)) {
File: LSP8IdentifiableDigitalAssetCore.sol 115: if (operator == address(0)) { 119: if (tokenOwner == operator) {
File: LSP8IdentifiableDigitalAssetCore.sol 139: if (operator == address(0)) { 143: if (tokenOwner == operator) {
File: LSP8IdentifiableDigitalAssetCore.sol 183: return (caller == tokenOwner || _operators[tokenId].contains(caller));
File: LSP8IdentifiableDigitalAssetCore.sol 319: if (to == address(0)) {
File: LSP8IdentifiableDigitalAssetCore.sol 401: if (from == to) { 410: if (to == address(0)) {
File: LSP8IdentifiableDigitalAssetInitAbstract.sol 49: interfaceId == _INTERFACEID_LSP8 ||
File: LSP8CappedSupplyInitAbstract.sol 26: if (tokenSupplyCap_ == 0) {
File: LSP8CompatibleERC721.sol 86: interfaceId == _INTERFACEID_ERC721 || 87: interfaceId == _INTERFACEID_ERC721METADATA ||
File: LSP8CompatibleERC721.sol 129: if (operatorListLength == 0) {
File: LSP8CompatibleERC721.sol 322: return retval == IERC721Receiver.onERC721Received.selector; 324: if (reason.length == 0) {
File: LSP8CompatibleERC721InitAbstract.sol 86: interfaceId == _INTERFACEID_ERC721 || 87: interfaceId == _INTERFACEID_ERC721METADATA ||
File: LSP8CompatibleERC721InitAbstract.sol 129: if (operatorListLength == 0) {
File: LSP8CompatibleERC721InitAbstract.sol 322: return retval == IERC721Receiver.onERC721Received.selector; 324: if (reason.length == 0) {
File: LSP8Enumerable.sol 36: if (from == address(0)) { 40: } else if (to == address(0)) {
File: LSP8EnumerableInitAbstract.sol 38: if (from == address(0)) { 42: } else if (to == address(0)) {
File: ERC725.sol 41: interfaceId == _INTERFACEID_ERC725X || 42: interfaceId == _INTERFACEID_ERC725Y ||
File: ERC725InitAbstract.sol 43: interfaceId == _INTERFACEID_ERC725X || 44: interfaceId == _INTERFACEID_ERC725Y ||
File: ERC725XCore.sol 68: interfaceId == _INTERFACEID_ERC725X ||
File: ERC725XCore.sol 83: if (operationType == OPERATION_0_CALL) { 88: if (operationType == OPERATION_1_CREATE) { 95: if (operationType == OPERATION_2_CREATE2) { 102: if (operationType == OPERATION_3_STATICCALL) { 119: if (operationType == OPERATION_4_DELEGATECALL) {
File: ERC725XCore.sol 139: (targets.length != values.length || values.length != datas.length) 144: if (operationsType.length == 0) {
File: ERC725XCore.sol 255: if (creationCode.length == 0) { 269: if (contractAddress == address(0)) {
File: ERC725XCore.sol 292: if (creationCode.length == 0) {
File: ERC725YCore.sol 84: if (dataKeys.length == 0) {
</details>File: ERC725YCore.sol 119: interfaceId == _INTERFACEID_ERC725Y ||
contract GasTest is DSTest { Contract0 c0; Contract1 c1; function setUp() public { c0 = new Contract0(); c1 = new Contract1(); } function testGas() public { c0.not_optimized(1,2); c1.optimized(1,2); } } contract Contract0 { function not_optimized(uint8 a,uint8 b) public returns(bool){ return ((a==1) || (b==1)); } } contract Contract1 { function optimized(uint8 a,uint8 b) public returns(bool){ return ((a ^ 1) & (b ^ 1)) == 0; } }
Contract0 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
46099 | 261 | ||||
Function Name | min | avg | median | max | # calls |
not_optimized | 456 | 456 | 456 | 456 | 1 |
Contract1 contract | |||||
---|---|---|---|---|---|
Deployment Cost | Deployment Size | ||||
42493 | 243 | ||||
Function Name | min | avg | median | max | # calls |
optimized | 430 | 430 | 430 | 430 | 1 |
#0 - c4-judge
2023-08-02T09:46:48Z
trust1995 marked the issue as grade-a