Asymmetry contest - T1MOH'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: 6/246

Findings: 4

Award: $988.40

QA:
grade-b

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: lukris02

Also found by: Bauer, HollaDieWaldfee, RedTiger, T1MOH, dec3ntraliz3d, joestakey, koxuan, qpzm, rbserver, reassor

Labels

bug
3 (High Risk)
low quality report
satisfactory
upgraded by judge
duplicate-641

Awards

236.4864 USDC - $236.49

External Links

Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/SfrxEth.sol#L108-L117

Vulnerability details

Impact

Inflates or lowers the price of SfrxEth / ETH depending on the oracle price

Proof of Concept

    /**
        @notice - Get price of derivative in terms of ETH
     */
    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());
    }

To get price SfrxEth / ETH we should calculate (SfrxEth / FrxEth) * (FrxEth / ETH). But in return formula we divide instead of multiply. For easy understanding suppose 1 SfrxEth costs 5 FrxEth and 1 FrxEth costs 0.2 ETH. It results in price SfrxEth / ETH = 5 / 0.2. Which means 25 instead of 1

Tools Used

Manual review

Refactor the formula

     function ethPerDerivative(uint256 _amount) public view returns (uint256) {
        uint256 frxAmount = IsFrxEth(SFRX_ETH_ADDRESS).convertToAssets(
            10 ** 18
        );
        return (frxAmount * IFrxEthEthPool(FRX_ETH_CRV_POOL_ADDRESS).price_oracle() / 10 ** 18);
    }

#0 - c4-pre-sort

2023-04-03T14:13:08Z

0xSorryNotSorry marked the issue as low quality report

#1 - c4-pre-sort

2023-04-04T16:50:38Z

0xSorryNotSorry marked the issue as duplicate of #698

#2 - c4-judge

2023-04-21T16:01:44Z

Picodes marked the issue as not a duplicate

#3 - c4-judge

2023-04-21T16:01:58Z

Picodes marked the issue as satisfactory

#4 - c4-judge

2023-04-21T16:02:08Z

Picodes marked the issue as duplicate of #641

#5 - c4-judge

2023-04-24T21:38:22Z

Picodes changed the severity to 3 (High Risk)

Findings Information

🌟 Selected for report: adriro

Also found by: 0x52, T1MOH, anodaram, cloudjunky, hassan-truscova

Labels

bug
3 (High Risk)
low quality report
satisfactory
upgraded by judge
duplicate-593

Awards

734.235 USDC - $734.24

External Links

Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/Reth.sol#L237-L241

Vulnerability details

Impact

Overflow in poolPrice() leads to revert and blocking of stake and unstake

Proof of Concept

        IUniswapV3Pool pool = IUniswapV3Pool(
            factory.getPool(rocketTokenRETHAddress, W_ETH_ADDRESS, 500)
        );
        (uint160 sqrtPriceX96, , , , , , ) = pool.slot0();
        return (sqrtPriceX96 * (uint(sqrtPriceX96)) * (1e18)) >> (96 * 2);

_sqrtPriceX96ToUint will only work when the non-fractional component of sqrtPriceX96 takes up to 2 bits. This represents a price ratio of 16. Market conditions are different, and can happen

Tools Used

Manual Review

Perform multiplication after converting numbers to 60x18

#0 - c4-pre-sort

2023-04-03T14:13:47Z

0xSorryNotSorry marked the issue as low quality report

#1 - c4-pre-sort

2023-04-04T14:44:19Z

0xSorryNotSorry marked the issue as duplicate of #693

#2 - c4-judge

2023-04-21T16:39:24Z

Picodes marked the issue as satisfactory

#3 - c4-judge

2023-04-24T21:39:48Z

Picodes changed the severity to 3 (High Risk)

Awards

4.5426 USDC - $4.54

Labels

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

External Links

Lines of code

https://github.com/code-423n4/2023-03-asymmetry/blob/main/contracts/SafEth/derivatives/WstEth.sol#L83-L88

Vulnerability details

Impact

Function returns price of WstEth in terms of stEth. But must be in terms of ETH.

Proof of Concept

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

Tools Used

Manual review

Convert amount of stEth into ETH via price_oracle in LIDO_CRV_POOL

#0 - c4-pre-sort

2023-04-03T14:11:44Z

0xSorryNotSorry marked the issue as low quality report

#1 - c4-pre-sort

2023-04-04T17:19:10Z

0xSorryNotSorry marked the issue as duplicate of #588

#2 - c4-judge

2023-04-21T17:09:53Z

Picodes marked the issue as satisfactory

#3 - c4-judge

2023-04-23T11:07:04Z

Picodes changed the severity to 3 (High Risk)

Mark SafEthStorage as abstract

This contract is dependent on SafEth.sol and provides read-only functionality, there is know sense in deploying it

Upgradeable contract is missing a __gap[50] storage variable to allow for new storage variables in later versions

See this link for a description of this storage variable. While some contracts may not currently be sub-classed, adding the variable now protects against forgetting to add it in the future. 1 instance: SafEthStorage

Use Ownable2StepUpgradeable instead of OwnableUpgradeable contract for SafEth

There is another Openzeppelin Ownable contract (Ownable2StepUpgradeable.sol) has transferOwnership function, use is more secure due to 2-stage ownership transfer.

Lack of functions for sending randomly received ETH and ERC-20 (apart from the derivatives themselves)

In WstEth.sol send balanceAfter - balanceBefore, but not all balance

        uint256 minOut = (stEthBal * (10 ** 18 - maxSlippage)) / 10 ** 18;
        uint256 balanceBefore = address(this).balance;
        IStEthEthPool(LIDO_CRV_POOL).exchange(1, 0, stEthBal, minOut);
        uint256 balanceAfter = address(this).balance;
        // solhint-disable-next-line
        (bool sent, ) = address(msg.sender).call{value: balanceAfter - balanceBefore}(
            ""
        );

For resque ERC-20 add

        uint256 balancePre = IERC20(STETH_TOKEN).balanceOf(address(this));
        IWStETH(WST_ETH).unwrap(_amount);
        uint256 balancePost = IERC20(STETH_TOKEN).balanceOf(address(this));
        uint256 stEthBal = balancePost - balancePre
        IERC20(STETH_TOKEN).approve(LIDO_CRV_POOL, stEthBal);

And by analogy provide resque for all derivatives

Unnecessary operation

Reth

        else return (poolPrice() * 10 ** 18) / (10 ** 18);

Return poolPrice() without unnecessary mul and div

#0 - c4-sponsor

2023-04-07T23:33:51Z

elmutt marked the issue as sponsor confirmed

#1 - c4-judge

2023-04-24T18:48:04Z

Picodes marked the issue as grade-b

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