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: 217/246
Findings: 1
Award: $8.84
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: RaymondFam
Also found by: 7siech, LeoGold, Phantasmagoria, SunSec, adeolu, anodaram, aviggiano, chaduke, d3e4, eyexploit, jasonxiale, juancito, koxuan, mojito_auditor, n33k, neumo, rotcivegaf, yac
8.8405 USDC - $8.84
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L115-L116
Divisions in EVM are rounded down, which means when the fraction price is close to 1 (e.g. 0.999) it would effectively be zero, when it's close to 2 (1.999) it would be rounded to 1 - loosing close to 50% of the intended price.
unstake
function divides result of multiplying (derivatives[i].balance() * _safEthAmount)
by safEthTotalSupply
to get derivativeAmount
. This can lead to loss of funds if the result of multiplying will be a floating-point number
115: uint256 derivativeAmount = (derivatives[i].balance() * 116: _safEthAmount) / safEthTotalSupply;
Example:
User has 3.5 safEth and he wants to exchange them on ETH. Let's say that safEthTotalSupply
is equals to 600 and every derivative balance is equal to 200 ETH. Then (200 * 3.5) / 600 = 1.16
But due to rounding it will be equal to 1 ETH
Additionally, it's worth noting that users who deposited less than 1 ETH may lose their funds. derivativeAmount
will always be zero, and funds will not be withdrawn due to the following check:
117: if (derivativeAmount == 0) continue; // if derivative empty ignore
And users safEth will be burned here:
120: _burn(msg.sender, _safEthAmount);
function unstake(uint256 _safEthAmount) external { require(pauseUnstaking == false, "unstaking is paused"); uint256 safEthTotalSupply = totalSupply(); uint256 ethAmountBefore = address(this).balance; 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); } _burn(msg.sender, _safEthAmount); uint256 ethAmountAfter = address(this).balance; uint256 ethAmountToWithdraw = ethAmountAfter - ethAmountBefore; // solhint-disable-next-line (bool sent, ) = address(msg.sender).call{value: ethAmountToWithdraw}( "" ); require(sent, "Failed to send Ether"); emit Unstaked(msg.sender, ethAmountToWithdraw, _safEthAmount); }
https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/SafEth.sol#L108-L129
Manual review
One way to implement rounding in Solidity is to multiply and divide by a power of 10 to shift the decimal point. For example, to round a value to two decimal places, you could multiply the value by 100, round the result using integer division, and then divide the result by 100. Here's an example:
function round(uint256 value, uint256 precision) internal pure returns (uint256) { uint256 factor = 10 ** precision; uint256 roundedValue = (value * factor) / factor; return roundedValue; }
In this function, the value
parameter represents the value to be rounded, and the precision
parameter represents the number of decimal places to round to. The function first calculates a factor
variable based on the desired precision, then multiplies the value
by the factor
to shift the decimal point, rounds the result using integer division, and then divides the result by the factor
to shift the decimal point back.
#0 - c4-pre-sort
2023-04-04T16:20:12Z
0xSorryNotSorry marked the issue as duplicate of #455
#1 - c4-judge
2023-04-24T20:57:44Z
Picodes marked the issue as partial-50
#2 - c4-judge
2023-04-24T21:10:02Z
Picodes changed the severity to 2 (Med Risk)