Asymmetry contest - auditor0517'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: 220/246

Findings: 2

Award: $4.68

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

4.5426 USDC - $4.54

Labels

bug
3 (High Risk)
satisfactory
upgraded by judge
duplicate-588

External Links

Lines of code

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

Vulnerability details

Impact

WstEth.ethPerDerivative returns stETH amount instead of ETH. stETH price is nearly same as ETH but not the same. At the moment of writing, 1ETH = 1.000965519156061458 stETH and ethPerDerivative will return a little different value.

Proof of Concept

WstEth.ethPerDerivative returns stETH amount while it should return ETH amount.

    function ethPerDerivative(uint256 _amount) public view returns (uint256) {
        return IWStETH(WST_ETH).getStETHByWstETH(10 ** 18);
    }

stETH is not the same as ETH in value at the moment. So WstEth.withdraw swaps stETH to ETH during withdrawal.

    IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);

The exact implementation is to get stETH price from the curve pool used in withdraw.

Tools Used

Manual Review

Get ETH value the curve pool. We can use get_dy method of the pool.

uint256 stETH = IERC20(WST_ETH).balanceOf(address(this)); return IStEthEthPool(LIDO_CRV_POOL).get_dy(1, 0, stETH);

#0 - c4-pre-sort

2023-04-04T17:13:51Z

0xSorryNotSorry marked the issue as duplicate of #588

#1 - c4-judge

2023-04-23T11:07:04Z

Picodes changed the severity to 3 (High Risk)

#2 - c4-judge

2023-04-24T20:45:05Z

Picodes marked the issue as satisfactory

Lines of code

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

Vulnerability details

Impact

Users can get more rETH by manipulating poolPrice() using flashloan.

Proof of Concept

Reth.deposit() swaps from ETH to rETH if the rETH pool is full by checking poolCanDeposit().

In this case, ETH will be swapped to rETH using poolPrice()

    function poolPrice() private view returns (uint256) {
        address rocketTokenRETHAddress = RocketStorageInterface(
            ROCKET_STORAGE_ADDRESS
        ).getAddress(
                keccak256(
                    abi.encodePacked("contract.address", "rocketTokenRETH")
                )
            );
        IUniswapV3Factory factory = IUniswapV3Factory(UNI_V3_FACTORY);
        IUniswapV3Pool pool = IUniswapV3Pool(
            factory.getPool(rocketTokenRETHAddress, W_ETH_ADDRESS, 500)
        );
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
        return (sqrtPriceX96 * (uint(sqrtPriceX96)) * (1e18)) >> (96 * 2);
    }

But it uses the raw price instead of TWAP so it can be manipulated easily.

  • An attacker noticed the rETH pool is full and Reth.deposit() will work with uniswap.
  • He starts a flashloan and drops down the ETH/rETH price of uniswap.
  • He deposits his ETH to the Reth contract and he gets more rETH than he should.
  • Afer that, he ends the flashloan.

Tools Used

Manual Review

We should use the TWAP price of uniswap instead of raw price.

#0 - c4-pre-sort

2023-04-04T11:34:46Z

0xSorryNotSorry marked the issue as duplicate of #601

#1 - c4-judge

2023-04-21T16:14:41Z

Picodes marked the issue as satisfactory

#2 - c4-judge

2023-04-21T16:15:17Z

Picodes marked the issue as duplicate of #1125

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