Platform: Code4rena
Start Date: 24/03/2023
Pot Size: $49,200 USDC
Total HM: 20
Participants: 246
Period: 6 days
Judge: Picodes
Total Solo HM: 1
Id: 226
League: ETH
Rank: 209/246
Findings: 1
Award: $10.79
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: Rolezn
Also found by: 0x3b, 0xGordita, 0xSmartContract, 0xhacksmithh, 0xnev, 0xpanicError, 4lulz, Angry_Mustache_Man, ArbitraryExecution, Aymen0909, Bason, BlueAlder, EvanW, Franfran, HHK, Haipls, IgorZuk, JCN, KrisApostolov, Madalad, MiksuJak, MiniGlome, RaymondFam, ReyAdmirado, Rickard, Sathish9098, Udsen, adriro, alexzoid, anodaram, arialblack14, c3phas, carlitox477, ch0bu, chaduke, codeslide, d3e4, dicethedev, ernestognw, fatherOfBlocks, georgits, hunter_w3b, inmarelibero, lukris02, mahdirostami, maxper, pavankv, pixpi, rotcivegaf, smaul, tank, tnevler, wen, yac
10.7864 USDC - $10.79
Issue | Instances | |
---|---|---|
[GAS-01] | The result of function calls should be cached rather than re-calling the function | 2 |
[GAS-02] | For loop can be replace by simple addition | 1 |
[GAS-03] | Check should be done before For loop | 1 |
[GAS-04] | Check should be done beforehand | 1 |
[GAS-05] | Unnecessary calculations | 1 |
[GAS-06] | Functions guaranteed to revert when called by normal users can be marked payable | 14 |
[GAS-07] | Increments can be unchecked | 1 |
[GAS-08] | Setting the constructor to payable | 4 |
The instances below point to the second+ call of the function within a single function.
Instances (2):
File: contracts/SafEth/SafEth.sol 74: derivatives[i].balance() 142: derivatives[i].withdraw(derivatives[i].balance());
Consider storing the value of derivatives[i].balance()
in a local variable instead of calling this function twice.
In the following code, the for loop can be replaced by a simple addition because only the _weight
is to be added to the totalWeight
.
Instance (1):
File: contracts/SafEth/SafEth.sol 190: uint256 localTotalWeight = 0; for (uint256 i = 0; i < derivativeCount; i++) localTotalWeight += weights[i]; totalWeight = localTotalWeight;
Can be replaced by:
totalWeight = totalWeight + _weight;
The check of ethAmountToRebalance
should be done before the for loop.
Instance (1):
File: contracts/SafEth/SafEth.sol 148: for (uint i = 0; i < derivativeCount; i++) { if (weights[i] == 0 || ethAmountToRebalance == 0) continue; // @audit edit this check uint256 ethAmount = (ethAmountToRebalance * weights[i]) / totalWeight; // Price will change due to slippage derivatives[i].deposit{value: ethAmount}(); }
Can be refactored as such:
if (ethAmountToRebalance != 0) { // @audit edit here for (uint i = 0; i < derivativeCount; i++) { if (weights[i] == 0) continue; // @audit edit here uint256 ethAmount = (ethAmountToRebalance * weights[i]) / totalWeight; // Price will change due to slippage derivatives[i].deposit{value: ethAmount}(); } }
Value check of weight
could be done just after the initialization of the variable.
Instance (1):
File: contracts/SafEth/SafEth.sol 85: uint256 weight = weights[i]; 86: IDerivative derivative = derivatives[i]; 87: if (weight == 0) continue; // @audit check should be done beforehand
Line 86 and 87 should be swapped.
Multiplication then division by the same number (10 ** 18) is unnecessary as it does not change the initial value.
Instance (1):
File: contracts/SafEth/derivatives/Reth.sol 215: else return (poolPrice() * 10 ** 18) / (10 ** 18);
This line can be replaced by:
else return poolPrice();
payable
If a function modifier or require such as onlyOwner/onlyX is used, the function will revert if a normal user tries to pay the function. Marking the function as payable will lower the gas cost for legitimate callers because the compiler will not include checks for whether a payment was provided. The extra opcodes avoided are CALLVALUE
(2),DUP1
(3),ISZERO
(3),PUSH2
(3),JUMPI
(10),PUSH1
(3),DUP1
(3),REVERT
(0),JUMPDEST
(1),POP
(2), which costs an average of about 21 gas per call to the function, in addition to the extra deployment cost.
Instances (14):
File: contracts/SafEth/SafEth.sol 138: function rebalanceToWeights() external onlyOwner { 214: function setMinAmount(uint256 _minAmount) external onlyOwner { 223: function setMaxAmount(uint256 _maxAmount) external onlyOwner { 232: function setPauseStaking(bool _pause) external onlyOwner { 241: function setPauseUnstaking(bool _pause) external onlyOwner {
File: contracts/SafEth/derivatives/Reth.sol 58: function setMaxSlippage(uint256 _slippage) external onlyOwner { 107: function withdraw(uint256 amount) external onlyOwner { 156: function deposit() external payable onlyOwner returns (uint256) {
File: contracts/SafEth/derivatives/SfrxEth.sol 51: function setMaxSlippage(uint256 _slippage) external onlyOwner { 60: function withdraw(uint256 _amount) external onlyOwner { 94: function deposit() external payable onlyOwner returns (uint256) {
File: contracts/SafEth/derivatives/WstEth.sol 48: function setMaxSlippage(uint256 _slippage) external onlyOwner { 56: function withdraw(uint256 _amount) external onlyOwner { 73: function deposit() external payable onlyOwner returns (uint256) {
unchecked
Increments for uint256 iterators cannot realistically overflow as this would require too many increments, so this can be unchecked
.
The unchecked
keyword is new in solidity version 0.8.0, so this only applies to that version or higher, which these instances are. This saves 30-40 gas PER LOOP.
Instances (1):
File: contracts/SafEth/SafEth.sol 188: derivativeCount++;
constructor
to payable
Saves ~13 gas per instance
Instances (4):
File: contracts/SafEth/SafEth.sol 38: constructor() {
File: contracts/SafEth/derivatives/Reth.sol 33: constructor() {
File: contracts/SafEth/derivatives/SfrxEth.sol 27: constructor() {
File: contracts/SafEth/derivatives/WstEth.sol 24: constructor() {
#0 - c4-sponsor
2023-04-10T21:00:53Z
elmutt marked the issue as sponsor confirmed
#1 - c4-judge
2023-04-23T19:12:31Z
Picodes marked the issue as grade-b