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: 25/246
Findings: 2
Award: $265.96
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: rbserver
Also found by: 0xAgro, DadeKuma, DeStinE21, HollaDieWaldfee, IgorZuk, J4de, P7N8ZK, Parad0x, Stiglitz, bytes032, carrotsmuggler, csanuragjain, dec3ntraliz3d, kaden, koxuan, lukris02, rvierdiiev, tnevler
29.4683 USDC - $29.47
A wrong derivative address can brick the staking and unstaking functionality in the contract, preventing further user interaction. Users cannot stake or unstake their tokens, causing a loss of functionality and potential financial impact.
The below function doesn't check for zero address or a contract address that conforms to the IDerivative interface.
function addDerivative( address _contractAddress, uint256 _weight ) external onlyOwner { derivatives[derivativeCount] = IDerivative(_contractAddress); weights[derivativeCount] = _weight; derivativeCount++; uint256 localTotalWeight = 0; for (uint256 i = 0; i < derivativeCount; i++) localTotalWeight += weights[i]; totalWeight = localTotalWeight; emit DerivativeAdded(_contractAddress, _weight, derivativeCount); }
The issue occurs when a wrong derivative address is added by owner. In the provided test cases, two scenarios were tested:
it("Should brick contract if address(0) is set as derivative contract address ", async function () { const depositAmount = ethers.utils.parseEther("10"); await safEthProxy.stake({ value: depositAmount }); // set zero address for derivative contract address await safEthProxy.addDerivative( ethers.constants.AddressZero, ethers.utils.parseEther("0") ); await expect(safEthProxy.unstake(ethers.utils.parseEther("1"))).to.be .reverted; // no more staking is possible. await expect(safEthProxy.stake({ value: 1 })).to.be.reverted; });
it("Should brick contract if a non conforming IDerivative address is set as derivative contract address ", async function () { const depositAmount = ethers.utils.parseEther("10"); await safEthProxy.stake({ value: depositAmount }); // set zero address for derivative contract address await safEthProxy.addDerivative( ethers.Wallet.createRandom().address, ethers.utils.parseEther("1") ); await expect(safEthProxy.unstake(ethers.utils.parseEther("1"))).to.be .reverted; // no more staking is possible. await expect(safEthProxy.stake({ value: 1 })).to.be.reverted; });
In both scenarios, the contract will lose its main functionality, staking and unstaking.
Manual
address(0)
check .IDerivative
interface.update
and/or remove
Derivative address.#0 - c4-pre-sort
2023-04-04T19:45:59Z
0xSorryNotSorry marked the issue as duplicate of #709
#1 - c4-judge
2023-04-23T12:02:56Z
Picodes marked the issue as duplicate of #703
#2 - c4-judge
2023-04-24T19:36:09Z
Picodes changed the severity to 3 (High Risk)
#3 - c4-judge
2023-04-24T21:24:39Z
Picodes marked the issue as partial-50
236.4864 USDC - $236.49
https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L111-L117 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/derivatives/SfrxEth.sol#L74-L75
The ethPerDerivative()
function uses Curve ETH-frxETH pool's price oracle. However, the calculation is incorrect. The value returned by the price oracle is the value of 1 frxETH in ETH, not the other way around.
function ethPerDerivative(uint256 _amount) public view returns (uint256) { uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets( 10 ** 18 ); return ((10 ** 18 * frxAmount) / IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle()); }
This issue will have side effects in other parts of the contract. For example, in the withdraw function, it uses ethPerDerivative()
function to calculate maximum slippage when swapping from frxETH to ETH.
uint256 minOut = (((ethPerDerivative(_amount) * _amount) / 10 ** 18) * (10 ** 18 - maxSlippage)) / 10 ** 18;
Let's say frxETH is trading at a 1% discount. Also, assume that 1 stakedFrxETH is 1.03 frxETH. As per current implementation, 1 stakedFrxETH will be:
(1.03 / 0.99) or ~1.04 ETH
Let's say we want to withdraw 1 stakedfrxETH, so:
minOut = 1 * 1.04 * 99% = ~1.03 ETH // 1% slippage
However, in reality, the maximum ETH that you can get for 1 stakedfrxETH is 1.03 * 0.99 or ~1.02. So the swap will fail even with just 1% discount.
Manual review.
To resolve this issue, update the ethPerDerivative() function to properly calculate the ETH value of a derivative. The corrected function should look like this:
function ethPerDerivative(uint256) public view returns (uint256) { uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets( 10 ** 18 ); - return ((10 ** 18 * frxAmount) / - IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle()); + + uint256 frxPriceInETH = IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS) + .price_oracle(); + return (frxAmount * frxPriceInETH) / 10 ** 18; }
#0 - c4-pre-sort
2023-04-04T16:55:07Z
0xSorryNotSorry marked the issue as duplicate of #698
#1 - c4-judge
2023-04-21T16:03:58Z
Picodes marked the issue as not a duplicate
#2 - c4-judge
2023-04-21T16:04:08Z
Picodes marked the issue as satisfactory
#3 - c4-judge
2023-04-21T16:04:16Z
Picodes marked the issue as duplicate of #641
#4 - c4-judge
2023-04-24T21:38:19Z
Picodes changed the severity to 3 (High Risk)