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: 110/246
Findings: 3
Award: $35.05
🌟 Selected for report: 0
🚀 Solo Findings: 0
11.1318 USDC - $11.13
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L63
If the SafEth
contract is deployed and there are no derivatives added to the contract and a user tries to call the stake
function, then this could result in a loss of funds for the user.
The following scenario could occur, which would result in the user having their Ether locked in the contract with no way of recovering their funds:
stake
function and sends 1 ether to stakederivativeCount
is 0totalStakeValueEth
will equal 0safETH
erc20 tokensThe following test can be written in the SafEth-Integration.test.ts
file to confirm this:
it("should result in user losing funds, while receiving 0 safEth tokens", async function () { // get SafEth contract const strategy = await getLatestContract(strategyContractAddress, "SafEth"); // get Bob's ether balance before staking const [bob] = await ethers.getSigners(); console.log("bob balance before: ", await ethers.provider.getBalance(bob.address)); // get Bob's safEth token balance before staking let bobTokensBefore = await strategy.connect(bob).balanceOf(bob.address); console.log("bob tokens before", bobTokensBefore) // Bob tries to stake 1 Ether when there are no derivative contracts added // get Bob's safEth token balance before staking await strategy.connect(bob).stake({value: ethers.utils.parseEther("1")}); // Bob's Ether balance will have decreased by at least 1 Ether console.log("bob balance after: ", await ethers.provider.getBalance(bob.address)); // Bob's safEth token's balance will remain unchanged, meaning Bob was minted 0 tokens for staking 1 Ether let bobTokensAfter = await strategy.connect(bob).balanceOf(bob.address); console.log("bob tokens after", bobTokensAfter) });
VS Code, Hardhat
Consider pausing the staking for the SafEth
contract in the initializer
function and only unpause the SafEth
contract once derivatives have been added.
#0 - c4-pre-sort
2023-04-04T19:16:41Z
0xSorryNotSorry marked the issue as duplicate of #363
#1 - c4-judge
2023-04-21T16:29:43Z
Picodes marked the issue as satisfactory
🌟 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
Contracts should be deployed with the same compiler version and flags that they have been tested with thoroughly. Locking the pragma helps to ensure that contracts do not accidentally get deployed using, for example, an outdated compiler version that might introduce bugs that affect the contract system negatively. https://swcregistry.io/docs/SWC-103
Floating Pragma List:
SafeEth.sol - ^0.8.13
Reth.sol - ^0.8.13
SfrxEth.sol - ^0.8.13
WstEth.sol - ^0.8.13
Consider locking the compiler pragma to the specific version of the Solidity compiler used during testing and development.
In the stake
function in SafEth.sol
, a uint256 totalSupply
variable is defined
and shadows the existing totalSupply()
function. This can be dangerous as any following code within
that context will lose the ability to call the totalSupply()
function.
It is best practice to avoid shadowing variables and functions.
Consider instead prefixing the local totalSupply
variable with an underscore to avoid shadowing the totalSupply()
function
In the initialize
function for SafEth.sol
, the _transferOwnership()
function is invoked to transfer the ownership to the deployer. The OpenZeppelin OwnableUpgradeable
library includes a __Ownable_init
function which has the same functionality but is intended to be used during initialization.
Consider replacing the _transferOwnership()
call with __Ownable_init()
in the initialize function to adhere to best practices set by the owners of the OwnableUpgradeable
library.
The IsFrxEth.sol
contract is imported in Reth.sol
but remains unused.
Consider removing all unused imports in order to keep code clean and readable.
SfrxEth.sol#61
SfrxEth.sol#98
SfrxEth.sol#102
SfrxEth.sol#112
SfrxEth.sol#123
WstEth.sol#57
WstEth.sol#74
WstEth.sol#78
WstEth.sol#87
WstEth.sol#94
WstEth.sol#58
WstEth.sol#59
To improve code clarity, the SFRX_ETH_ADDRESS
and FRX_ETH_ADDRESS
in SfrxEth.sol
could be
declared as a IsFrxEth
type. Additionally, a balanceOf(address)
function could be added to
IsFrxEth.sol
. This would remove the need for IsFrxEth
and IERC20
casts.
To improve code clarity, the WST_ETH
in WstEth.sol
could be declared as a IWStETH
type.
This would remove the need for IWStETH
and IERC20
casts.
To improve code clarity the STETH_TOKEN
in WstEth.sol
could be declared as a IERC20
type.
This would remove the need for IERC20
casts.
Consider saving the aforementioned addresses as their interface counterparts, as well as including any required functions or interfaces to the existing interfaces.
In the addDerivative
function of the SafeEth
contract, there is not a check to see if the _contractAddress
parameter is actually a contract addresss.
Adding any deployed contract address that do not conform to the IDerivative
interface can prevent the whole protocol from working.
Consider checking for codeAt
any derivative contract added with the addDerivative
function, or implementing an ERC165
supports interface to ensure only valid derivative contracts are added.
Both the adjustWeight
and setMaxSlippage
functions can be called on non-existent derivatives.
In order to restrict inputs into the contract, consider requiring the index set to be a valid derivative for both the adjustWeight
and setMaxSlippage
functions.
approve
external call result not checked[SfrxEth.sol#69-72](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L69-L72 [WstEth.sol#59](https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/WstEth.sol#L59 Reth.sol#90
The withdraw function in SfrxEth.sol calls the approve function on FRX_ETH_ADDRESS but the return value is not checked to ensure success The withdraw function in WstEth.sol calls the approve function on STETH_TOKEN but the return value is not checked to ensure success The swapExactInputSingleHop function in Reth.sol calls the approve function on UNISWAP_ROUTER but the return value is not checked to ensure success
Consider instead prefixing the local totalSupply variable with an underscore to avoid shadowing the totalSupply() function
uint
Throughout the SafEth.sol
contract and in one place in the Reth.sol
contract, the uint
term is used instead of uint256
.
It is recommended to use the uint256
keyword over the uint
keyword for explicitness. Consider changing all instances of uint
to uint256
to keep the code consistent.
In the withdraw function in WstEth.so
l and SfrxEth.so
l the value 10 ** 18
is hardcoded and used to calculate
the correct number of minimum tokens. This currently works as expected but the STETH_TOKEN
address is a proxy and as such it is possible the decimals field may change in the future which
would break the calculation and could set minOut
to a value that is too high or too low.
#0 - c4-sponsor
2023-04-07T21:22:40Z
toshiSat marked the issue as sponsor confirmed
#1 - c4-judge
2023-04-24T17:02:20Z
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
balanceOf
callGas savings in the deposit
function in SfrxEth.sol
can be had by returning the return value from the submitAndDeposit
external call. This would eliminate two external balanceOf
external calls, as well as the subtraction operation.
balanceOf
callGas savings in the withdraw
function in WstEth.sol
can be had by saving off the return value of the unwrap
call and using that return value instead of making an additional balanceOf
external call to the STETH_TOKEN
address.
The following loop could be made more gas efficient by changing the derivatives
variable type:
for (uint i = 0; i < derivativeCount; i++) underlyingValue += (derivatives[i].ethPerDerivative(derivatives[i].balance()) * derivatives[i].balance()) / 10 ** 18;
A storage pointer can't be used here because its a mapping so the elements won't be contiguous.
If the derivatives
variable is changed from a mapping to an array a storage pointer could be used and would result in significant gas savings.
#0 - c4-sponsor
2023-04-07T21:26:02Z
toshiSat marked the issue as sponsor acknowledged
#1 - c4-judge
2023-04-23T14:38:12Z
Picodes marked the issue as grade-b