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.