Omniscia Euler Finance Audit
Set Code Style Findings
Set Code Style Findings
STE-01C: Discrepancy of Proposed Maximum Element Limitation
| Type | Severity | Location |
|---|---|---|
| Standard Conformity | ![]() | Set.sol:L31, L42, L85, L87 |
Description:
The Set::MAX_ELEMENTS constant specifies that the value must not exceed 255, however, a value equal to 255 would permit writes up to index 254 to occur for the elements entry of a SetStorage.
As such, the last element of the SetStorage::elements key would never be written to whose index would be 255.
Impact:
The length of the SetStorage::elements entry is inefficient and will always contain an extraneous slot.
Example:
16/// @title SetStorage17/// @notice This struct is used to store the set data.18/// @dev To optimize the gas consumption, firstElement is stored in the same storage slot as the numElements19/// @dev so that for sets with one element, only one storage slot has to be read/written. To keep the elements20/// @dev array indexing consistent and because the first element is stored outside of the array, the elements[0]21/// @dev is not utilized. The stamp field is used to keep the storage slot non-zero when the element is removed.22/// @dev It allows for cheaper SSTORE when an element is inserted.23struct SetStorage {24 /// @notice The number of elements in the set.25 uint8 numElements;26 /// @notice The first element in the set.27 address firstElement;28 /// @notice The stamp of the set.29 uint88 stamp;30 /// @notice The array of elements in the set. Stores the elements starting from index 1.31 ElementStorage[2 ** 8] elements;32}33
34/// @title Set35/// @author Euler Labs (https://www.eulerlabs.com/)36/// @notice This library provides functions for managing sets of addresses.37/// @dev The maximum number of elements in the set is defined by the constant MAX_ELEMENTS.38library Set {39 error TooManyElements();40 error InvalidIndex();41
42 uint8 internal constant MAX_ELEMENTS = 10; // must not exceed 255Recommendation:
As the SetStorage::numElements entry is constrained to the uint8 data type, we advise the SetStorage::elements array length to be configured as 2 ** 8 - 1, or type(uint8).max, properly illustrating the maximum elements that the SetStorage::elements could practically contain.
Alleviation (577d1991de2aa6c1b0578e9e45d363e8d6799408):
As part of exhibit's STE-04C remediations, a consistent constant is utilized for both the SetStorage::elements array as well as the code's original MAX_ELEMENTS constant.
As such, we consider this exhibit indirectly addressed as extraneous slots are no longer present.
STE-02C: Inexistent Avoidance of Bit Masking (Stamp & First Element Missing)
| Type | Severity | Location |
|---|---|---|
| Gas Optimization | ![]() | Set.sol:L138, L141 |
Description:
The referenced statements are meant to avoid Solidity's compiler-generated bit-masking, however, this is not presently achieved. The code will partially assign to a 256-bit slot, and thus will result in bit masking to be generated by the compiler.
Example:
130// set numElements for every execution path to avoid SSTORE and bit masking when the element removed is131// firstElement132if (searchIndex != lastIndex) {133 if (searchIndex == 0) {134 setStorage.firstElement = setStorage.elements[lastIndex].value;135 setStorage.numElements = uint8(lastIndex);136 } else {137 setStorage.elements[searchIndex].value = setStorage.elements[lastIndex].value;138 setStorage.numElements = uint8(lastIndex);139 }140} else {141 setStorage.numElements = uint8(lastIndex);142}Recommendation:
We advise the referenced statements to be accompanied by assignments to the setStorage.firstElement and setStorage.stamp as the firstElement value already exists in memory, optimizing the code's compiler-incurred gas cost.
Alleviation (577d1991de2aa6c1b0578e9e45d363e8d6799408):
The relevant statements have been introduced around the numElements assignments, optimizing their compiler-generated gas cost as originally intended.
STE-03C: Inexistent Avoidance of Bit Masking (Stamp Missing)
| Type | Severity | Location |
|---|---|---|
| Gas Optimization | ![]() | Set.sol:L119-L121, L134-L135 |
Description:
The Set::remove function will attempt to minimize gas costs by instructing the compiler to overwrite a storage slot in full instead of overwriting sub-sections of it which entail compiler-generated bit masking.
While the first highlighted instance correctly achieves this by "redundantly" re-assigning the stamp thus instructing the compiler that we need a fresh value for it, the second referenced instance does not do this.
Example:
117// write full slot at once to avoid SLOAD and bit masking118if (numElements == 1) {119 setStorage.numElements = 0;120 setStorage.firstElement = address(0);121 setStorage.stamp = DUMMY_STAMP;122 return true;123}124
125uint256 lastIndex;126unchecked {127 lastIndex = numElements - 1;128}129
130// set numElements for every execution path to avoid SSTORE and bit masking when the element removed is131// firstElement132if (searchIndex != lastIndex) {133 if (searchIndex == 0) {134 setStorage.firstElement = setStorage.elements[lastIndex].value;135 setStorage.numElements = uint8(lastIndex);136 } else {137 setStorage.elements[searchIndex].value = setStorage.elements[lastIndex].value;138 setStorage.numElements = uint8(lastIndex);139 }140} else {141 setStorage.numElements = uint8(lastIndex);142}Recommendation:
We advise the stamp to be assigned to in the second referenced instance as well, optimizing the first element's removal gas cost.
Alleviation (577d1991de2aa6c1b0578e9e45d363e8d6799408):
An assignment of the stamp member has been introduced in the highlighted code segment, ensuring that the compiler efficiently generates the setStorage data update.
STE-04C: Inexistent Correlation of Constants
| Type | Severity | Location |
|---|---|---|
| Code Style | ![]() | Set.sol:L31, L42 |
Description:
The 2 ** 8 constant which is utilized to instantiate the length of the elements array does not depend on the MAX_ELEMENTS member although a relation should be enforced.
Example:
16/// @title SetStorage17/// @notice This struct is used to store the set data.18/// @dev To optimize the gas consumption, firstElement is stored in the same storage slot as the numElements19/// @dev so that for sets with one element, only one storage slot has to be read/written. To keep the elements20/// @dev array indexing consistent and because the first element is stored outside of the array, the elements[0]21/// @dev is not utilized. The stamp field is used to keep the storage slot non-zero when the element is removed.22/// @dev It allows for cheaper SSTORE when an element is inserted.23struct SetStorage {24 /// @notice The number of elements in the set.25 uint8 numElements;26 /// @notice The first element in the set.27 address firstElement;28 /// @notice The stamp of the set.29 uint88 stamp;30 /// @notice The array of elements in the set. Stores the elements starting from index 1.31 ElementStorage[2 ** 8] elements;32}33
34/// @title Set35/// @author Euler Labs (https://www.eulerlabs.com/)36/// @notice This library provides functions for managing sets of addresses.37/// @dev The maximum number of elements in the set is defined by the constant MAX_ELEMENTS.38library Set {39 error TooManyElements();40 error InvalidIndex();41
42 uint8 internal constant MAX_ELEMENTS = 10; // must not exceed 255Recommendation:
We advise the constant declaration to be relocated outside the Set library, and the SetStorage structure to utilize the constant as the length of the ElementStorage member optimizing the maintainability of the codebase.
Alleviation (577d1991de2aa6c1b0578e9e45d363e8d6799408):
The code was updated to ensure a consistent maximum element limit is imposed across the contract via the SET_MAX_ELEMENTS constant which is configured at 10 in accordance to our recommendation.
STE-05C: Optimization of Interim Element Removal
| Type | Severity | Location |
|---|---|---|
| Gas Optimization | ![]() | Set.sol:L137, L144 |
Description:
An optimization can be achieved by caching the setStorage.elements[lastIndex] storage offset which is calculated twice redundantly whenever an element is removed in between the elements array.
Impact:
To note, the adjustment is applicable to all instances but should result in a gas optimization solely for the interim element case as the first-element removal will re-calculate the setStorage.elements[lastIndex] offset using a keccak256 instruction on the same payload which has reduced gas costs at the EVM level. This is not the case for interim removals, which also calculate the keccak256 offset of the setStorage.elements[searchIndex] entry.
Example:
130// set numElements for every execution path to avoid SSTORE and bit masking when the element removed is131// firstElement132if (searchIndex != lastIndex) {133 if (searchIndex == 0) {134 setStorage.firstElement = setStorage.elements[lastIndex].value;135 setStorage.numElements = uint8(lastIndex);136 } else {137 setStorage.elements[searchIndex].value = setStorage.elements[lastIndex].value;138 setStorage.numElements = uint8(lastIndex);139 }140} else {141 setStorage.numElements = uint8(lastIndex);142}143
144setStorage.elements[lastIndex].value = address(0);Recommendation:
We applied this optimization and observed a median decrease in gas cost and thus advise it to be applied.
Alleviation (577d1991de2aa6c1b0578e9e45d363e8d6799408):
The setStorage.elements[lastIndex] lookup is properly cached and re-used across its previous instances, optimizing the code's median gas cost as advised.
