Asymmetry contest - hihen's results

A protocol to help diversify and decentralize liquid staking derivatives.

General Information

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

Asymmetry Finance

Findings Distribution

Researcher Performance

Rank: 79/246

Findings: 3

Award: $61.46

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

8.2654 USDC - $8.27

Labels

bug
2 (Med Risk)
satisfactory
duplicate-770

External Links

Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L113-L119 https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L140-L143

Vulnerability details

Impact

If the withdraw function of any derivative fails, the unstake() and rebalanceToWeights() functions will always revert, resulting in all staked assets being frozen.

Proof of Concept

In the unstake() and rebalanceToWeights() functions, all derivatives' withdraw() functions are called (as long as the amount involved is not 0). Therefore, if the withdraw() function of any derivative reverts, the entire transaction will revert.

function unstake(uint256 _safEthAmount) external { ... 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); } ... } function rebalanceToWeights() external onlyOwner { ... for (uint i = 0; i < derivativeCount; i++) { if (derivatives[i].balance() > 0) derivatives[i].withdraw(derivatives[i].balance()); } ... }

Although theoretically the likelihood of a derivative.withdraw() failure is not common, this possibility exists, and there are quite a few risk points:

  • Any underlying derivative contract errors: such as vulnerabilities, upgrades, or hacker attacks, etc.
  • Withdrawal-related DEX (Uniswap, Curve) errors: such as vulnerabilities, upgrades, or hacker attacks, or liquidity pool liquidity anomalies, etc.

In other words, any of these external factors can cause all staked ETH in SafEth to be frozen, which is too risky and the consequences are too severe.

Moreover, the problem cannot be solved by setting the weight of the faulty derivatives to zero

Tools Used

VS Code

We can add a parameter to the unstake() and rebalanceToWeights(): address[] skipDerivatives, allowing users to provide some derivatives to be ignored in those special cases, so that assets that are not affected can be safely unstaked/withdrawn.

#0 - c4-pre-sort

2023-04-04T17:30:05Z

0xSorryNotSorry marked the issue as duplicate of #703

#1 - c4-pre-sort

2023-04-04T20:14:40Z

0xSorryNotSorry marked the issue as not a duplicate

#2 - c4-pre-sort

2023-04-04T20:15:07Z

0xSorryNotSorry marked the issue as duplicate of #770

#3 - c4-judge

2023-04-21T15:05:50Z

Picodes marked the issue as satisfactory

Awards

11.1318 USDC - $11.13

Labels

bug
2 (Med Risk)
satisfactory
duplicate-363

External Links

Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/44b5cd94ebedc187a08884a7f685e950e987261c/contracts/SafEth/SafEth.sol#L63

Vulnerability details

Impact

If no derivative contracts have been added to SafEth yet and a user calls the SafEth.stake(), all ETH will be locked in the contract.

Proof of Concept

After SafEth is deployed, pauseStaking is set to false by default, and users can call the SafEth.stake() successfully at this time.

However, since the derivatives need to be added manually by the owner one by one, derivatives are empty at this point (i.e. derivativeCount = 0), it will cause the following issues in stake():

  • The ETH transferred (msg.value) with the tx is not deposited into any derivative contract.
  • totalStakeValueEth and mintAmount are always 0, so 0 SafEth tokens will be minted to the msg.sender.

In other words, all the ETH staked by the user is locked in the SafEth contract, and the user receives 0 SafEth tokens.

Tools Used

VS Code

We should add require(derivativeCount > 0, "derivatives is empty"); in stake() to prevent this issue.

Additionally, it may be considered to set pauseStaking = true in SafEth.initialize(), and call SafEth.setPauseStaking() after the configuration is complete(such as adding derivatives).

#0 - c4-pre-sort

2023-04-04T19:10:55Z

0xSorryNotSorry marked the issue as duplicate of #363

#1 - c4-judge

2023-04-21T16:32:00Z

Picodes marked the issue as satisfactory

AuditHub

A portfolio for auditors, a security profile for protocols, a hub for web3 security.

Built bymalatrax © 2024

Auditors

Browse

Contests

Browse

Get in touch

ContactTwitter