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: 138/246
Findings: 2
Award: $23.92
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: brgltd
Also found by: 0x3b, 0xAgro, 0xGusMcCrae, 0xNorman, 0xRajkumar, 0xSmartContract, 0xTraub, 0xWagmi, 0xWaitress, 0xffchain, 0xhacksmithh, 0xkazim, 0xnev, 3dgeville, ArbitraryExecution, Aymen0909, BRONZEDISC, Bason, Bloqarl, BlueAlder, Brenzee, CodeFoxInc, CodingNameKiki, Cryptor, DadeKuma, DevABDee, Diana, Dug, Englave, Gde, Haipls, HollaDieWaldfee, Ignite, Infect3d, Jerry0x, Josiah, Kaysoft, Koko1912, KrisApostolov, Lavishq, LeoGold, Madalad, PNS, Rappie, RaymondFam, RedTiger, Rickard, Rolezn, Sathish9098, SunSec, T1MOH, UdarTeam, Udsen, Viktor_Cortess, Wander, adriro, ak1, alejandrocovrr, alexzoid, arialblack14, ayden, bin2chen, brevis, btk, c3phas, carlitox477, catellatech, ch0bu, chaduke, ck, climber2002, codeslide, descharre, dingo2077, ernestognw, fatherOfBlocks, favelanky, georgits, helios, hl_, inmarelibero, juancito, ks__xxxxx, lopotras, lukris02, m_Rassska, mahdirostami, maxper, nadin, navinavu, nemveer, p_crypt0, peanuts, pipoca, pixpi, qpzm, rbserver, reassor, roelio, rotcivegaf, scokaf, siddhpurakaran, slvDev, smaul, tnevler, tsvetanovv, turvy_fuzz, vagrant, wen, yac, zzzitron
13.1298 USDC - $13.13
There is no functionality of the contract to remove a derivative from the index fund. In the case where an incorrect derivative is deployed or an underlying derivative is compromised and is no longer wanted to be used there is no way to remove this from the contract.
Although this can be mitigated by setting the weight of the derivative to 0 this will become useless storage on the contract and will increase the overall cost of operations on the contract when it has to iterate through all derivatives.
Implement a removeDerivative() function which will remove the derivative from
the contract. This will have implications on how derivatives are added the
contract since it uses the derivativeCount
variable to create new entries in
the derivative mapping.
Gas griefing is possible on external calls as the return data has to be stored even if it is omitted, due to the EVM architecture:
(bool sent,) = address(msg.sender).call{value: ethAmountToWithdraw}("");
To mitigate this, perform a low level call with out and outdata size set to 0 so that the return data is not stored.
@@ -108,7 +108,9 @@ contract SafEth is Initializable, ERC20Upgradeable, OwnableUpgradeable, SafEthSt uint256 ethAmountAfter = address(this).balance; uint256 ethAmountToWithdraw = ethAmountAfter - ethAmountBefore; // solhint-disable-next-line - (bool sent,) = address(msg.sender).call{value: ethAmountToWithdraw}(""); + assembly { + sent := call(gas(), caller(), ethAmountToWithdraw, 0, 0, 0, 0) + } require(sent, "Failed to send Ether"); emit Unstaked(msg.sender, ethAmountToWithdraw, _safEthAmount); }
Check this tweet for more details: https://twitter.com/pashovkrum/status/1607024043718316032?t=xs30iD6ORWtE2bTTYsCFIQ&s=19
The transferOwnership
function is used to change owners of the contract. This
is using OZ's OwnableUpgradeable
access-control contract.
There is a more secure contract called Ownable2StepUpgradeable
which requires
the new owner to accept the ownership. This is safer and more secure as it
prevents ownership being sent to a bad address.
This is mostly applicable to the SafEth contract, but can be applied to the derivatives, however the derivatives owners are unlikely to change.
#0 - c4-sponsor
2023-04-10T17:55:52Z
toshiSat marked the issue as sponsor acknowledged
#1 - c4-judge
2023-04-24T17:37:38Z
Picodes marked the issue as grade-b
🌟 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
addDerivative
In the addDerivative
function, the total weight of all the
derivatives is recalculated by iterating through the weights
storage variable
and individually adding these all up in a for loop.
This is not required since addDerivative
is only adding 1 weight, and not
adjusting the existing ones. So you can simply add the new weight to the
existing totalWeight with:
totalWeight = totalWeight + _weight;
Gas saving is 522 gas and increases with the number of derivatives.
adjustWeight
Similar to above, the adjustWeight
function is only changing 1 weight and so
we can alter the totalWeight
variable directly before altering the weights
mapping to get the calculation.
totalWeight = totalWeight - weights[_derivativeIndex] + _weight; weights[_derivativeIndex] = _weight;
Gas saving is 154 and increases with the number of derivatives.
## [G-03] Loop variable in for loop can be unchecked Overflowing the loop variable in may for loops is very unlikely when iterating derivatives so we can remove the overflow check to save some gas. This is because most loops start at 0 and increment by 1 and will never reach the max uint256 or will run out of gas by then. This will save about 50 gas per iteration. For example in the `unstake()` function: ```sol for (uint256 i = 0; i < derivativeCount; i++) { // withdraw a percentage of each asset based on the amount of safETH uint256 derivativeAmount = (derivatives[i].balance() * _safEthAmount) / safEthTotalSupply; if (derivativeAmount == 0) continue; // if derivative empty ignore derivatives[i].withdraw(derivativeAmount); }
Should be changed too
diff --git a/contracts/SafEth/SafEth.sol b/contracts/SafEth/SafEth.sol index 849d456..0db8b3b 100644 --- a/contracts/SafEth/SafEth.sol +++ b/contracts/SafEth/SafEth.sol @@ -98,11 +98,14 @@ contract SafEth is Initializable, ERC20Upgradeable, OwnableUpgradeable, SafEthSt uint256 safEthTotalSupply = totalSupply(); uint256 ethAmountBefore = address(this).balance; - for (uint256 i = 0; i < derivativeCount; i++) { + for (uint256 i = 0; i < derivativeCount;) { // withdraw a percentage of each asset based on the amount of safETH uint256 derivativeAmount = (derivatives[i].balance() * _safEthAmount) / safEthTotalSupply; if (derivativeAmount == 0) continue; // if derivative empty ignore derivatives[i].withdraw(derivativeAmount); + unchecked { + ++i; + } }
Other places to update:
#0 - c4-sponsor
2023-04-10T21:08:17Z
toshiSat marked the issue as sponsor acknowledged
#1 - c4-judge
2023-04-23T19:11:53Z
Picodes marked the issue as grade-b