Platform: Code4rena
Start Date: 08/01/2024
Pot Size: $83,600 USDC
Total HM: 23
Participants: 116
Period: 10 days
Judge: 0xean
Total Solo HM: 1
Id: 317
League: ETH
Rank: 62/116
Findings: 1
Award: $42.44
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: 0xAnah
Also found by: 0x11singh99, 0xhex, 0xta, SAQ, mgf15, shamsulhaq123, sivanesh_808, slvDev
42.4402 USDC - $42.44
S.No | Title |
---|---|
G-01 | Gas Optimization in deploy Function Redefine Inline Assembly in Create2Deployer.sol |
G-02 | Enhanced Arithmetic Efficiency in _calculatePaymentProRata Function in PaymentEscrow.sol |
G-03 | Streamlining Arithmetic in _calculateFee Function in PaymentEscrow.sol |
G-04 | Optimization of Inline Assembly _insert in Accumulator.sol |
G-05 | Optimization of Inline Assembly _convertToStatic in Accumulator.sol |
G-06 | Optimization Report for _validateFulfiller and _validateProtocolSignatureExpiration Functions in Signer.sol |
G-07 | In-Function Optimization of ABI Encoding in _reclaimRentedItems() in Stop.sol |
G-08 | Optimization of addRentalSafe Function in Storage Contract |
G-09 | Optimization of isRentedOut Function in Storage Contract |
G-10 | Optimizing Hashing Operations with Assembly in _deriveDomainSeparator and _deriveTypehashes in Signer.sol |
deploy
Function Redefine Inline AssemblyDescription:
The objective of this optimization is to enhance the gas efficiency of the deploy
function in a Solidity smart contract using refined inline assembly techniques. The original function uses the create2
opcode for deploying contracts, but the assembly code can be restructured for better gas management.
Original Code:
function deployOriginal(bytes memory initCode, bytes32 salt) public payable returns (address deploymentAddress) { assembly { deploymentAddress := create2( callvalue(), add(initCode, 0x20), mload(initCode), salt ) } }
Optimized Code:
function deployOptimized(bytes memory initCode, bytes32 salt) public payable returns (address deploymentAddress) { assembly { let ptr := mload(0x40) // Free memory pointer mstore(ptr, callvalue()) // Store callvalue at ptr mstore(add(ptr, 0x20), add(initCode, 0x20)) // Store pointer to initCode mstore(add(ptr, 0x40), mload(initCode)) // Store length of initCode mstore(add(ptr, 0x60), salt) // Store salt deploymentAddress := create2( callvalue(), // Endowment (value) passed to the created contract ptr, // Memory start pointer 0x80, // Memory length salt // Salt value ) } }
Explanation of Optimization:
Organized Memory Management:
mload(0x40)
) to create an organized block of memory for the create2
inputs.create2
call is stored in a contiguous memory block, which can be more efficient for the EVM to process.Sequential Input Storage:
create2
function is stored sequentially in the allocated memory. This not only makes the code clearer but also potentially reduces the computational overhead associated with calculating offsets for each parameter.Reduced Redundant Computations:
add(initCode, 0x20)
are done within the create2
call, potentially leading to redundant computations. In the optimized code, these calculations are performed once and stored, reducing the number of operations required.Explicit Memory Length Specification:
0x80
) for the create2
call, which includes the lengths of the callvalue
, init code pointer, init code length, and salt. This precise specification helps in ensuring that only the necessary memory is used.Impact on Gas Consumption:
_calculatePaymentProRata
FunctionThe _calculatePaymentProRata
function, crucial in the PaymentEscrow
smart contract, currently calculates pro-rata payments between a renter and a lender. This report suggests a further optimization to the function's calculation logic, aiming at reducing gas consumption by simplifying the arithmetic operations.
function _calculatePaymentProRata( uint256 amount, uint256 elapsedTime, uint256 totalTime ) internal pure returns (uint256 renterAmount, uint256 lenderAmount) { uint256 numerator = (amount * elapsedTime) * 1000; renterAmount = ((numerator / totalTime) + 500) / 1000; lenderAmount = amount - renterAmount; }
function _calculatePaymentProRata( uint256 amount, uint256 elapsedTime, uint256 totalTime ) internal pure returns (uint256 renterAmount, uint256 lenderAmount) { renterAmount = (amount * elapsedTime + totalTime / 2) / totalTime; lenderAmount = amount - renterAmount; }
Optimization of Division and Rounding Logic:
amount * elapsedTime
by 1000, divides by totalTime
, adds 500 (for rounding), and then divides by 1000 again.totalTime / 2
before the division to handle rounding. This significantly reduces the number of arithmetic operations.Simplified Rounding Technique:
totalTime / 2
before the division is a standard technique for rounding to the nearest integer in integer division, replacing the more complex original approach.Elimination of Intermediate Variables:
numerator
variable is removed, as the calculation is streamlined into a single line. This reduces the overhead and makes the code more concise.Gas Efficiency:
Maintaining Business Logic:
Testing and Safety:
_calculateFee
FunctionThe _calculateFee
function in the PaymentEscrow
smart contract is designed to calculate fees based on a given amount. This report suggests an optimization in its arithmetic logic to potentially reduce gas consumption.
function _calculateFee(uint256 amount) internal view returns (uint256) { return (amount * fee) / 10000; }
function _calculateFee(uint256 amount) internal view returns (uint256) { // Optimized calculation return amount / (10000 / fee); }
Rearranging the Arithmetic Operation:
amount
by fee
and then divides by 10000. This involves a multiplication followed by a division.fee
first and then divides the amount
by this result. This changes the order of operations to potentially reduce the computational load.Gas Efficiency:
fee
is significantly smaller than 10000.Maintaining Precision:
10000 / fee
does not lead to significant precision loss. It's crucial to analyze and test to ensure that the calculation still maintains acceptable accuracy for the contract's use case.Testing and Safety:
amount
or fee
is very large or small.Potential Overflow Check:
fee
can be very small, leading to a large divisor._insert
Accumulator.sol
Description:
This report focuses on optimizing the inline assembly code used in the Accumulator
contract. Inline assembly is a powerful feature in Solidity that allows for low-level operations and can lead to significant gas savings when used correctly. The optimization suggestions are intended to enhance the efficiency of memory operations and arithmetic computations without altering the contract's fundamental logic.
Original Code:
The Accumulator
contract contains two primary functions with inline assembly - _insert
and _convertToStatic
. These functions handle dynamic memory operations and complex data structuring for managing RentalAssetUpdate
structs.
Optimization Recommendations:
Optimization in _insert
Function:
let newByteDataSize := add(mload(rentalAssets), 0x40) mstore(rentalAssets, newByteDataSize)
mstore(rentalAssets, add(mload(rentalAssets), 0x40))
newByteDataSize
and the subsequent mstore
operation into a single line. It reduces the number of operations by eliminating the need for a separate variable to hold newByteDataSize
.Reusing Calculated Values:
elements
and newItemPosition
if they are recalculated elsewhere in the assembly block.Simplifying Arithmetic Expressions:
Optimization in _convertToStatic
Function:
rentalAssetUpdateLength
. Loop optimization can include minimizing the number of operations within the loop and using efficient arithmetic operations.General Assembly Optimizations:
mstore8
for single-byte operations, if applicable, and minimize the scope of variables to reduce stack operations._convertToStatic
Accumulator.sol
To optimize the _convertToStatic
function without separating it into a different function, we'll focus on streamlining the inline assembly operations within the same function. This approach will maintain the logic within a single function block, potentially reducing function call overhead and simplifying the contract structure.
_convertToStatic
Function// Original Code function _convertToStatic( bytes memory rentalAssetUpdates ) internal pure returns (RentalAssetUpdate[] memory updates) { bytes32 rentalAssetUpdatePointer; assembly { rentalAssetUpdatePointer := add(0x20, rentalAssetUpdates) rentalAssetUpdateLength := mload(rentalAssetUpdatePointer) } updates = new RentalAssetUpdate[](rentalAssetUpdateLength); for (uint256 i = 0; i < rentalAssetUpdateLength; ++i) { RentalId rentalId; uint256 amount; assembly { let currentElementOffset := add(0x20, mul(i, 0x40)) rentalId := mload(add(rentalAssetUpdatePointer, currentElementOffset)) amount := mload( add(0x20, add(rentalAssetUpdatePointer, currentElementOffset)) ) } updates[i] = RentalAssetUpdate(rentalId, amount); } }
_convertToStatic
Function// Optimized Code function _convertToStatic( bytes memory rentalAssetUpdates ) internal pure returns (RentalAssetUpdate[] memory updates) { uint256 rentalAssetUpdateLength; assembly { rentalAssetUpdateLength := mload(add(rentalAssetUpdates, 0x20)) } updates = new RentalAssetUpdate[](rentalAssetUpdateLength); for (uint256 i = 0; i < rentalAssetUpdateLength; ++i) { uint256 offset = 0x20 + i * 0x40; uint256 rentalId; uint256 amount; assembly { rentalId := mload(add(rentalAssetUpdates, offset)) amount := mload(add(rentalAssetUpdates, add(offset, 0x20))) } updates[i] = RentalAssetUpdate(rentalId, amount); } }
Inline Assembly within Loop:
rentalId
and amount
from the byte array. This minimizes function calls and keeps the logic concise within the loop.Efficient Memory Access:
rentalId
and amount
are more efficient than higher-level Solidity operations, potentially reducing gas costs, especially for larger arrays.Reduced Computational Overhead:
RentalAssetUpdate
struct within the byte array is done within the loop but outside the assembly block. This approach reduces computational steps in the assembly code, focusing it solely on memory operations._validateFulfiller
and _validateProtocolSignatureExpiration
FunctionsThis report outlines proposed optimizations for two functions in the specified Solidity smart contract: _validateFulfiller
and _validateProtocolSignatureExpiration
. Both functions currently perform single condition checks. The suggested optimizations involve refactoring these functions into modifiers, which can lead to reduced gas consumption by minimizing function call overhead.
_validateFulfiller
:function _validateFulfiller( address intendedFulfiller, address actualFulfiller ) internal pure { if (intendedFulfiller != actualFulfiller) { revert Errors.SignerPackage_UnauthorizedFulfiller( actualFulfiller, intendedFulfiller ); } }
_validateFulfiller
:modifier onlyIntendedFulfiller(address intendedFulfiller) { require(msg.sender == intendedFulfiller, "Unauthorized Fulfiller"); _; }
_validateFulfiller
:modifier
, which is more gas-efficient for simple condition checks._validateProtocolSignatureExpiration
:function _validateProtocolSignatureExpiration(uint256 expiration) internal view { if (block.timestamp > expiration) { revert Errors.SignerPackage_SignatureExpired(block.timestamp, expiration); } }
_validateProtocolSignatureExpiration
:modifier notExpired(uint256 expiration) { require(block.timestamp <= expiration, "Signature Expired"); _; }
_validateProtocolSignatureExpiration
:_validateFulfiller
, this function is refactored into a modifier
._reclaimRentedItems()
The _reclaimRentedItems()
function plays a pivotal role in the Stop
contract, specifically in handling the execution of transactions to retrieve rented items. The primary focus of this optimization report is on the ABI encoding process utilized within the function. The original implementation employs abi.encodeWithSelector
, which, while straightforward, may not be the most gas-efficient approach. This report presents an optimized solution that incorporates a more efficient encoding technique directly within the function, eliminating the need for additional helper functions.
function _reclaimRentedItems(RentalOrder memory order) internal { bool success = ISafe(order.rentalWallet).execTransactionFromModule( address(this), 0, abi.encodeWithSelector(this.reclaimRentalOrder.selector, order), Enum.Operation.DelegateCall ); if (!success) { revert Errors.StopPolicy_ReclaimFailed(); } }
function _reclaimRentedItems(RentalOrder memory order) internal { // Directly using the selector from the function's signature bytes4 selector = this.reclaimRentalOrder.selector; // Manually combining the selector with the encoded parameters using abi.encodePacked bytes memory encodedData = abi.encodePacked(selector, abi.encode(order)); bool success = ISafe(order.rentalWallet).execTransactionFromModule( address(this), 0, encodedData, Enum.Operation.DelegateCall ); if (!success) { revert Errors.StopPolicy_ReclaimFailed(); } }
abi.encodePacked()
for ABI encoding, replacing abi.encodeWithSelector()
. This method combines the function selector with the encoded order parameters in a more gas-efficient manner._reclaimRentedItems()
, the code is streamlined, avoiding the overhead of an additional function call.abi.encodePacked()
, which allows for a compact data representation, potentially leading to gas savings.addRentalSafe
Function in Storage
ContractDescription:
The addRentalSafe
function in the Storage
contract is responsible for adding a new rental safe address to storage and incrementing the total count of safes. The current implementation uses an additional variable newSafeCount
to calculate the incremented value of totalSafes
. This report suggests an optimization to reduce gas consumption by directly incrementing the totalSafes
state variable.
Original Code:
function addRentalSafe(address safe) external onlyByProxy permissioned { // Get the new safe count. uint256 newSafeCount = totalSafes + 1; // Register the safe as deployed. deployedSafes[safe] = newSafeCount; // Increment nonce. totalSafes = newSafeCount; }
Optimized Code:
function addRentalSafe(address safe) external onlyByProxy permissioned { // Increment totalSafes and use it for registering the safe. deployedSafes[safe] = ++totalSafes; }
Optimization Explanation:
The optimization involves the removal of the temporary variable newSafeCount
and directly incrementing the totalSafes
state variable. This change reduces the gas cost associated with declaring and assigning a new variable. By using the pre-increment (++totalSafes
), the state variable totalSafes
is incremented before it is used to update deployedSafes[safe]
. This approach simplifies the function and reduces the operations performed on the Ethereum Virtual Machine (EVM), leading to lower gas consumption.
isRentedOut
Function in Storage
ContractDescription:
The isRentedOut
function in the Storage
contract is designed to check if an asset is actively being rented by a wallet. Currently, the function constructs a RentalId
structure to use as a key for accessing the rentedAssets
mapping. This report proposes an optimization to reduce gas usage by directly accessing the rentedAssets
mapping without constructing the RentalId
structure.
Original Code:
function isRentedOut( address recipient, address token, uint256 identifier ) external view returns (bool) { // calculate the rental ID RentalId rentalId = RentalUtils.getItemPointer(recipient, token, identifier); // Determine if there is a positive amount return rentedAssets[rentalId] != 0; }
Optimized Code:
function isRentedOut( address recipient, address token, uint256 identifier ) external view returns (bool) { // Directly use the composite key components to access the mapping return rentedAssets[keccak256(abi.encodePacked(recipient, token, identifier))] != 0; }
Optimization Explanation:
The optimization removes the need to create a RentalId
structure and instead uses a hashed combination of recipient
, token
, and identifier
as the key for the rentedAssets
mapping. This is achieved by utilizing keccak256
and abi.encodePacked
to hash the concatenated values of the function parameters, forming a unique key. This approach reduces the computational overhead associated with struct creation and manipulation, leading to lower gas consumption for the function execution.
_deriveDomainSeparator
and _deriveTypehashes
Signer.sol
The _deriveDomainSeparator
and _deriveTypehashes
functions in the Signer
contract are responsible for deriving hashes crucial for the contract's functionality. These functions currently use Solidity's built-in hashing functions. This report proposes an optimization technique using inline assembly for hashing operations, which can potentially reduce gas costs.
// In _deriveTypehashes function nameHash = keccak256(bytes(_NAME)); versionHash = keccak256(bytes(_VERSION)); eip712DomainTypehash = keccak256( abi.encodePacked( "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)" ) ); // In _deriveDomainSeparator function domainSeparator = keccak256( abi.encode( _eip712DomainTypeHash, _nameHash, _versionHash, block.chainid, address(this) ) );
// Optimized _deriveTypehashes function assembly { nameHash := keccak256(add(_NAME, 32), mload(_NAME)) versionHash := keccak256(add(_VERSION, 32), mload(_VERSION)) let typeStr := mload(0x40) // Allocate memory mstore(typeStr, "EIP712Domain(string name,string version,uint256 chainId,address verifyingContract)") eip712DomainTypehash := keccak256(typeStr, 71) // Length of the string above } // Optimized _deriveDomainSeparator function assembly { let data := mload(0x40) // Allocate memory mstore(data, _eip712DomainTypeHash) mstore(add(data, 32), _nameHash) mstore(add(data, 64), _versionHash) mstore(add(data, 96), chainid()) mstore(add(data, 128), address()) domainSeparator := keccak256(data, 160) // 5 * 32 bytes }
#0 - c4-pre-sort
2024-01-21T18:26:20Z
141345 marked the issue as sufficient quality report
#1 - 141345
2024-01-22T08:43:14Z
554 sivanesh_808 l r nc 0 0 10
G 1 n G 2 n G 3 n G 4 n G 5 n G 6 n G 7 n G 8 n G 9 n G 10 n
#2 - c4-judge
2024-01-27T20:21:53Z
0xean marked the issue as grade-b