Omniscia Steer Protocol Audit
FeeManager Manual Review Findings
FeeManager Manual Review Findings
FMR-01M: Discrepant Sanitization of Total Vault Fees
Type | Severity | Location |
---|---|---|
Input Sanitization | FeeManager.sol:L114, L152, L208 |
Description:
Each entry written to the vaultTotalFees
is sanitized differently or even remains unsanitized depending on the function invoked.
Impact:
The vaultTotalFees
does not abide by a specific range of values, and is even unsanitized in the case of the FeeManager::setDefaultFeeAndWithdrawalPermission
function which we consider invalid.
Example:
204function setTotalFees(205 address vault,206 uint256 newTotalFees207) external onlyOwner {208 require(newTotalFees < 5000, "Total fee must be less than 5000");209 vaultTotalFees[vault] = newTotalFees;210}
Recommendation:
We advise validation of the vaultTotalFees
entry to be streamlined across the contract, ensuring a single limitation is imposed on it across all functions that overwrite it.
Alleviation (6513a21a002d422e298719b22f73a4559dfd4663):
Total fee validation across the contract has been streamlined to validate an upper bound of 10000
, alleviating this exhibit.
FMR-02M: Inexistent Prevention of Duplicate Fee Identifiers
Type | Severity | Location |
---|---|---|
Logical Fault | FeeManager.sol:L77, L107 |
Description:
The FeeManager::setFeeAndWithdrawalPermission
and FeeManager::setDefaultFeeAndWithdrawalPermission
functions do not prevent duplicate identifiers from being specified, a trait that will result in a discrepancy for the withdrawalPermissions
entry.
Impact:
Duplicate fee identifiers in one of the two aforementioned configuration calls will result in the final withdrawer
of the duplicate entries to remain in the withdrawalPermissions
entry, a trait we consider invalid.
Example:
58function setFeeAndWithdrawalPermission(59 address vault,60 string[] memory feeIdentifier,61 uint256[] memory feeValue,62 address[] memory withdrawer63) external onlyOwner {64 require(65 feeIdentifier.length == feeValue.length &&66 feeValue.length == withdrawer.length,67 "Input arrays must have the same length"68 );69 uint256 totalFeeValue;70 if (vaultFees[vault].length > 0) delete vaultFees[vault]; //Always make to pass all the tiers again when adding more tiers71 for (uint256 i = 0; i < feeIdentifier.length; i++) {72 require(73 withdrawer[i] != address(0),74 "Withdrawer address cannot be zero"75 );76 vaultFees[vault].push(Fee(feeIdentifier[i], feeValue[i]));77 withdrawalPermissions[vault][feeIdentifier[i]] = withdrawer[i];78 totalFeeValue += feeValue[i];79 }80 require(81 totalFeeValue == 10000,82 "Total fee value must be exactly 10000"83 );84 emit FeeUpdated(vault, feeIdentifier, feeValue, withdrawer);85}86
87function setDefaultFeeAndWithdrawalPermission(88 address vault,89 uint256 totalVaultFees,90 string[] memory feeIdentifier,91 uint256[] memory feeValue,92 address[] memory withdrawer93) external {94 require(vaultRegistry == msg.sender, "!VaultRegistry");95 require(96 feeIdentifier.length == feeValue.length &&97 feeValue.length == withdrawer.length,98 "Input arrays must have the same length"99 );100 uint256 totalFeeValue;101 for (uint256 i = 0; i < feeIdentifier.length; i++) {102 require(103 withdrawer[i] != address(0),104 "Withdrawer address cannot be zero"105 );106 vaultFees[vault].push(Fee(feeIdentifier[i], feeValue[i]));107 withdrawalPermissions[vault][feeIdentifier[i]] = withdrawer[i];108 totalFeeValue += feeValue[i];109 }110 require(111 totalFeeValue == 10000,112 "Total fee value must be exactly 10000"113 );114 vaultTotalFees[vault] = totalVaultFees;115 emit FeeUpdated(vault, feeIdentifier, feeValue, withdrawer);116}
Recommendation:
We advise the code to ensure that each feeIdentifier
is unique.
This can be achieved by ensuring that their input order is strictly ascending by evaluating each identifier's keccak256
hash, or by using a new withdrawalPermissions
subset on each re-configuration and ensuring each entry being written to was previously zero.
Alleviation (6513a21a00):
A FeeManager::areAllUnique
function has been introduced to the contract that will ensure all fee identifiers are unique by comparing them in looping iterations.
The validation mechanism can be made significantly more efficient by ensuring that the keccak256
results of the strings are in strictly ascending manner, permitting uniqueness validation in just a single iteration.
Alleviation (dc34c6a066):
The Steer Protocol team evaluated our recommendation, and believe that the operational burden imposed by off-chain keccak256
based ordering does not outweigh the gas benefits attached to it.
As the original vulnerability described has been alleviated, we consider this exhibit adequately addressed.
FMR-03M: Out-of-Bound Index Access Failures
Type | Severity | Location |
---|---|---|
Logical Fault | FeeManager.sol:L270-L275 |
Description:
The FeeManager::withdrawMultipleFees
function will fail to execute if any of the amount
prefixed arrays have a length
of 0
, as the code will attempt to write to them in such a case.
Impact:
The functionality of the code that automatically calculates the accrued fees for a particular fee identifier is inaccessible due to unconditional out-of-bound array assignments.
Example:
247function withdrawMultipleFees(248 address[] memory vaults,249 string[] memory feeIdentifiers,250 uint256[] memory amount0,251 uint256[] memory amount1252) public {253 require(254 vaults.length == feeIdentifiers.length,255 "Vaults and feeIdentifiers length mismatch"256 );257 address[] memory to;258 if (amount0.length > 0 || amount1.length > 0) {259 for (uint256 i = 0; i < vaults.length; i++) {260 to[i] = withdrawalPermissions[vaults[i]][feeIdentifiers[i]];261 IMultiPositionManager(vaults[i]).collectFees(262 feeIdentifiers[i],263 amount0[i],264 amount1[i]265 );266 }267 } else {268 for (uint256 i = 0; i < vaults.length; i++) {269 to[i] = withdrawalPermissions[vaults[i]][feeIdentifiers[i]];270 amount0[i] = IMultiPositionManager(vaults[i]).accruedFees0(271 feeIdentifiers[i]272 );273 amount1[i] = IMultiPositionManager(vaults[i]).accruedFees1(274 feeIdentifiers[i]275 );276 IMultiPositionManager(vaults[i]).collectFees(277 feeIdentifiers[i],278 amount0[i],279 amount1[i]280 );281 }282 }283 emit FeesWithdrawn(284 msg.sender,285 vaults,286 feeIdentifiers,287 to,288 amount0,289 amount1290 );291}
Recommendation:
We advise the code to re-instantiate the amount0
and amount1
arrays in the else
branch of the FeeManager::withdrawMultipleFees
function, ensuring that the assignments that ensue are correctly performed.
Alleviation (6513a21a002d422e298719b22f73a4559dfd4663):
The code will correctly re-instantiate the amt0
and amt1
arrays to permit the vault index based assignments to be performed safely, alleviating this exhibit.