Omniscia Steer Protocol Audit
SmartRewardsDistributor Code Style Findings
SmartRewardsDistributor Code Style Findings
SRD-01C: Combination of Data Structures
| Type | Severity | Location |
|---|---|---|
| Gas Optimization | ![]() | SmartRewardsDistributor.sol:L66, L69, L71, L77 |
Description:
The referenced data structures all utilize a campaignId as a key either directly or in between their resolution.
Example:
66mapping(uint256 => RewardCampaign) public rewardCampaigns;67
68// Total spent by a campaign, a check used to track if campaign is over allocating69mapping(uint256 => uint256) public campaignCredit;70
71mapping(uint256 => bool) public pausedCampaigns;72
73address public orchestratorRelay;74
75/// @dev Mapping of cumulative amount claimed by each address for each campaignId.76/// user - campaignId - amount77mapping(address => mapping(uint256 => uint256)) public claims;Recommendation:
We advise all of them to be combined under the existing RewardCampaign structure, greatly optimizing their gas cost when they are utilized together by minimizing the number of mapping lookups that need to be performed.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The campaign credit as well as whether it has been paused has been properly introduced to the RewardCampaign structure whilst the claims entry has been retained separate.
As the former two of the exhibit's referenced data structures were considered more relevant to the RewardCampaign structure, we consider this exhibit fully addressed.
SRD-02C: Deprecated Revert Pattern
| Type | Severity | Location |
|---|---|---|
| Code Style | ![]() | SmartRewardsDistributor.sol:L126-L131, L359-L362 |
Description:
The referenced if-revert statements will issue a revert statement with a string literal which has been deprecated.
Example:
354/// @dev for emergencies or misRegisters, cancel campaign should be used in most circumstances355function pauseCampaign(uint campaignId) external returns (bool) {356 RewardCampaign memory campaign = rewardCampaigns[campaignId];357 require(msg.sender == campaign.owner || msg.sender == owner(), "Must be campaign owner to pause");358 bool state = !pausedCampaigns[campaignId];359 if (campaign.cancelBlock != 0 && !state) {360 // Pausing a canceled campaign locks it, cannot be unpaused361 revert("Cannot unpause canceled campaign!");362 }363 pausedCampaigns[campaignId] = state;364 emit CampaignPaused(campaignId, msg.sender, state);365 return state;366}367
368modifier onlyOrchestrator() {369 // This function reverts if msg.sender is not a keeper370 require(msg.sender == orchestratorRelay, "Orchestrator call via dynamic vault only");371 _;372}Recommendation:
We advise custom error declarations to be utilized instead, or the if-revert patterns to be updated to require checks in uniformity with other require checks in the SmartRewardsDistributor contract.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The referenced if-revert checks have been properly converted to require checks, addressing this exhibit.
SRD-03C: Generic Typographic Mistake
| Type | Severity | Location |
|---|---|---|
| Code Style | ![]() | SmartRewardsDistributor.sol:L65 |
Description:
The referenced line contains a typographical mistake (i.e. private variable without an underscore prefix) or generic documentational error (i.e. copy-paste) that should be corrected.
Example:
65CountersUpgradeable.Counter public _rewardCampaignIdTracker;Recommendation:
We advise this to be done so to enhance the legibility of the codebase.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The underscore prefix has been properly removed from the public variable, addressing this exhibit.
SRD-04C: Ineffectual Usage of Safe Arithmetics
| Type | Severity | Location |
|---|---|---|
| Language Specific | ![]() | SmartRewardsDistributor.sol:L205, L345, L346 |
Description:
The linked mathematical operation is guaranteed to be performed safely by surrounding conditionals evaluated in either require checks or if-else constructs.
Example:
205uint256 campaignFee = steerFee >= discount ? steerFee - discount : 0;Recommendation:
Given that safe arithmetics are toggled on by default in pragma versions of 0.8.X, we advise the linked statement to be wrapped in an unchecked code block thereby optimizing its execution cost.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The referenced calculations have all been wrapped in an unchecked code block safely, optimizing the code's gas cost.
SRD-05C: Inefficient mapping Lookups
| Type | Severity | Location |
|---|---|---|
| Gas Optimization | ![]() | SmartRewardsDistributor.sol:L153, L160, L328, L331 |
Description:
The linked statements perform key-based lookup operations on mapping declarations from storage multiple times for the same key redundantly.
Example:
153uint256 outbound = amount - claims[user][id];154if (outbound == 0) {155 continue;156}157// credit campaign & check amount158uint256 totalCampaignCredit = campaignCredit[id] + outbound;159require(getAvailableFundsUpToBlock(campaign, lastBlockUpdate) >= totalCampaignCredit, "Campaign allocation exceeds distribution, check if halted!");160claims[user][id] = amount;Recommendation:
As the lookups internally perform an expensive keccak256 operation, we advise the lookups to be cached wherever possible to a single local declaration that either holds the value of the mapping in case of primitive types or holds a storage pointer to the struct contained.
As the compiler's optimizations may take care of these caching operations automatically at-times, we advise the optimization to be selectively applied, tested, and then fully adopted to ensure that the proposed caching model indeed leads to a reduction in gas costs.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The latter of the two pairs has been optimized, however, the former remains suboptimal.
To note, the recommendation of this exhibit is to store the claims[user] lookup to a local mapping(uint256 => uint256) storage variable which is permitted and would significantly optimize the loop's gas cost.
SRD-06C: Inexistent Error Message
| Type | Severity | Location |
|---|---|---|
| Code Style | ![]() | SmartRewardsDistributor.sol:L326 |
Description:
The linked require check has no error message explicitly defined.
Example:
326require(newOwner != address(0));Recommendation:
We advise one to be set so to increase the legibility of the codebase and aid in validating the require check's condition.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
A descriptive error message has been introduced for the referenced require check, addressing this exhibit.
SRD-07C: Misleading Maximum Fee
| Type | Severity | Location |
|---|---|---|
| Code Style | ![]() | SmartRewardsDistributor.sol:L52 |
Description:
The underscore separator as utilized infers that the maximum fee would be 10%, however, the maximum steer fee is 100% which we consider incorrect.
Example:
52uint256 internal constant MAX_STEER_FEE = 10_000;53uint256 internal constant FEE_DIVISOR = 100_00;Recommendation:
We advise one trailing zero to be removed from the MAX_STEER_FEE variable, properly enforcing a maximum limitation of 10%.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The maximum fee variable has been updated from 10_000 (being equivalent to 100%) to 25_00 (being equivalent to 25%), ensuring that it illustrates the limitation correctly.
SRD-08C: Optimization of Condition
| Type | Severity | Location |
|---|---|---|
| Gas Optimization | ![]() | SmartRewardsDistributor.sol:L143, L164 |
Description:
The SmartRewardsDistributor::claim function will repeatedly evaluate whether the caller is the OwnableUpgradeable::owner of the contract redundantly.
Example:
133address caller = msg.sender;134address own = owner();135
136for (uint256 i; i < arrayLength; i++ ) {137 uint256 id = campaignIds[i];138 RewardCampaign memory campaign = rewardCampaigns[id];139 uint256 amount = amounts[i];140 address user = users[i];141
142 // allow owner to withdraw unhalted paused funds143 if (pausedCampaigns[id] && caller != own) {continue;}144
145 bytes32 leaf = keccak256(abi.encodePacked(user, id, amount));146 bool isValidLeaf = MerkleProofUpgradeable.verify(147 proofs[i],148 merkleRoot,149 leaf150 );151 require(isValidLeaf, "Calculated Merkle Hash is invalid");152
153 uint256 outbound = amount - claims[user][id];154 if (outbound == 0) {155 continue;156 }157 // credit campaign & check amount158 uint256 totalCampaignCredit = campaignCredit[id] + outbound;159 require(getAvailableFundsUpToBlock(campaign, lastBlockUpdate) >= totalCampaignCredit, "Campaign allocation exceeds distribution, check if halted!");160 claims[user][id] = amount;161 // Check moved here to presersve root hash and accounting162
163 164 if (caller == own) {165 // allow owner to take custody only if rewards abandoned166 require(block.number >= campaign.abandonmentDeadlineBlock, "Custody not transfered yet");167 user = own;168 }169
170 IERC20(campaign.rewardToken).transfer(user, outbound);171 emit UserClaim(user, campaign.rewardToken, outbound, id, totalCampaignCredit);172}Recommendation:
We advise this comparison to be performed once and stored as a bool that is consequently reused within the for loop, optimizing its gas cost.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The msg.sender == owner() conditional is cached outside the for loop, optimizing its usage and thus the function's gas cost.
SRD-09C: Redundant Parenthesis Statement
| Type | Severity | Location |
|---|---|---|
| Code Style | ![]() | SmartRewardsDistributor.sol:L389-L391 |
Description:
The referenced statement is redundantly wrapped in parenthesis (()).
Example:
389(keccak256(lastChar) != keccak256(bytes("O")) &&390 keccak256(lastChar) != keccak256(bytes("I")) &&391 keccak256(lastChar) != keccak256(bytes("l")));Recommendation:
We advise them to be safely omitted, increasing the legibility of the codebase.
Alleviation (e31556397b9d321737ff47809b572383dda4a2aa):
The redundant parenthesis in the referenced statement have been safely omitted.
