Y2k Finance contest - scaraven's results

A suite of structured products for assessing pegged asset risk.

General Information

Platform: Code4rena

Start Date: 14/09/2022

Pot Size: $50,000 USDC

Total HM: 25

Participants: 110

Period: 5 days

Judge: hickuphh3

Total Solo HM: 9

Id: 162

League: ETH

Y2k Finance

Findings Distribution

Researcher Performance

Rank: 35/110

Findings: 4

Award: $167.09

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

36.6124 USDC - $36.61

Labels

bug
duplicate
3 (High Risk)
oracle
decimals
satisfactory

External Links

Lines of code

https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/oracles/PegOracle.sol#L67-L82

Vulnerability details

Impact

PegOracle combines two different Chainlink oracles, thereby allowing the protocol to directly compare prices of two different tokens. Chainlink has two main types of oracles, X/USD pairs and X/ETH pairs. The former uses 8 decimals, while the latter uses 18 decimals. PegOracle correctly handles X/USD pairs however incorrectly handles X/ETH pairs and therefore would incorrectly report any price which is derived from ETH pairs.

This will cause ETH pairs to produce prices which are too low and therefore vaults will automatically be registered as depegged even if the underlying assets have not depegged allowing hedge users to unfairly profit.

Proof of Concept

  1. Admin deploys a new PegOracle based on ETH-pairs where decimals is initialised to be 18
        decimals = priceFeed1.decimals();
  1. When latestRoundData() is called, nowPrice is calculated and has 4 decimals due to price2 and price1 cancelling each other's decimals
            //@audit 18 + 4 - 18
            nowPrice = (price2 * 10000) / price1;
  1. As priceFeed1.decimals() has 18 decimals, decimals10 becomes 1
        int256 decimals10 = int256(10**(18 - priceFeed1.decimals()));
  1. This means that nowPrice stays at 4 decimals and when it is returned it now has -2 decimals even though PegOracle.decimals() = 18
            //4 - 6 = -2 decimals
            nowPrice / 1000000
  1. Therefore, for all relative prices under 100x, price rounds down to 0 which is incorrect

The current formula for the number of decimals of the returned price is 16 - x where x = priceFeed1.decimals(). Therefore, for 8 decimals (USD pairs) PegOracle is correct.

Tools Used

VS Code

Rewrite latestRoundData() to:

    function latestRoundData()
        public
        view
        returns (
            uint80 roundID,
            int256 nowPrice,
            uint256 startedAt,
            uint256 timeStamp,
            uint80 answeredInRound
        )
    {
        (
            uint80 roundID1,
            int256 price1,
            uint256 startedAt1,
            uint256 timeStamp1,
            uint80 answeredInRound1
        ) = priceFeed1.latestRoundData();


        int256 price2 = getOracle2_Price();


        if (price1 > price2) {
            nowPrice = (price2 * decimals) / price1;
        } else {
            nowPrice = (price1 * decimals) / price2;
        }



        return (
            roundID1,
            nowPrice,
            startedAt1,
            timeStamp1,
            answeredInRound1
        );
    }

#0 - scaraven

2022-09-20T09:18:59Z

I was in a bit of a rush when submitting this and made a mistake, the fix should be nowPrice = (price2 * 10**(decimals)) / price1;

#1 - HickupHH3

2022-10-18T03:38:47Z

dup of #195

Findings Information

🌟 Selected for report: hyh

Also found by: 0x4non, 0xNazgul, Haruxe, KIntern_NA, PwnPatrol, Respx, Tointer, joestakey, pauliax, peritoflores, rotcivegaf, scaraven

Labels

bug
duplicate
3 (High Risk)
satisfactory

Awards

85.8509 USDC - $85.85

External Links

Lines of code

https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/Vault.sol#L217 https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/rewards/StakingRewards.sol#L99-L105 https://github.com/OpenZeppelin/openzeppelin-contracts/blob/7a14f6c5953a1f2228280e6eb1dfee8e5c28d79a/contracts/token/ERC1155/ERC1155.sol#L125

Vulnerability details

Impact

In Vault.sol, after an epoch has ended (or a depeg event has occurred), users can withdraw() on behalf of other users as long as the receiver of asset has been approved by the owner. Such receiver's can also include StakingRewards.sol if a user has staked their assets because users must approve the StakingRewards contract so that they can stake(). Therefore, a malicious user can burn ERC1155 shares of users who have staked and send the resulting funds to the StakingRewards contract.

In order for this to work, users must have staked and withdrawn from StakingRewards without withdrawing from Vault.sol (though it is not impossible that an attacker frontruns the second action) Funds are not permanently lost as the asset funds can be recovered through recoverERC20() but this is dependent on the owner to properly distribute funds back to affected users. Alternatively, the owners can use this to steal funds for themselves as an indirect rug pull vector.

Proof of Concept

  1. Alice takes part in a market and invests 100 WETH into a vault and receives 100 ERC1155 tokens
  2. Alice approves StakingRewards and stakes her tokens into StakingRewards through stake()
  3. After a certain amount of time, Alice exits her position and withdraws her ERC1155 tokens from StakingRewards
  4. It is unlikely Alice has revoked her approval and so Bob calls withdraw() on behalf of Alice where recipient is StakingRewards
  5. Alice's funds from the vault are sent to StakingRewards where they are temporarily locked

Tools Used

VS Code

Consider disallowing users who have no approval to withdraw on behalf of other users. Instead, only allow the owners or approved operators e.g.

        if(
            msg.sender != owner &&
            isApprovedForAll(owner, msg.sender) == false)
            revert OwnerDidNotAuthorize(msg.sender, owner);

#1 - HickupHH3

2022-10-17T03:36:31Z

Valid attack vector and impact well explained

Findings Information

Awards

8.0071 USDC - $8.01

Labels

bug
duplicate
2 (Med Risk)
resolved

External Links

Lines of code

https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/oracles/PegOracle.sol#L58-L63 https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/Controller.sol#L308-L309 https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/oracles/PegOracle.sol#L103

Vulnerability details

Impact

In PegOracle.latestRoundData(), when priceFeed1.latestRoundData() is called there are no checks whether the data is stale or not. This could lead to the oracle reporting incorrect prices which can negatively affect hedge and risk users, e.g. the oracle reports a price of 1.0 even if a depeg has occurred.

Additionally, even though all other sections of code which use oracles do check that answeredInRound >= round, it may be the case that the chain link nodes have stopped working and therefore not updated to a new round in a very long time causing the data to be stale.

Tools Used

VS Code

Add checks for staleness of data including checking the timestamp of the data e.g.

            uint80 roundID1,
            int256 price1,
            ,
            uint256 timeStamp1,
            uint80 answeredInRound1
        ) = priceFeed1.latestRoundData();


        require(price1 > 0, "Chainlink price <= 0");
        require(
            answeredInRound1 >= roundID1,
            "RoundID from Oracle is outdated!"
        );
        //@audit make sure that the timestamp is not from too long ago
        require(timeStamp1 > block.timestamp - OracleWindow, "Timestamp is too low");

#0 - HickupHH3

2022-10-15T07:25:40Z

dup of #61

Lines of code

https://github.com/code-423n4/2022-09-y2k-finance/blob/2175c044af98509261e4147edeb48e1036773771/src/VaultFactory.sol#L295-L374

Vulnerability details

Impact

Throughout most contracts, a single user is given control over changing important parameters e.g. VaultFactory.setController(). If this user is at any point malicious, then it is very easy for the user to manipulate parameters in a way to steal funds from normal users e.g. setting a malicious oracle which incorrectly reports a depeg

It is highly recommended to either implement delays when changing parameters so that users have time to react to changes which may seem malicious and/or making sure that a multi-sig wallet or DAO control the contracts

#0 - MiguelBits

2022-09-29T23:53:15Z

this user admin is intended to be a multi-sig

#1 - HickupHH3

2022-10-29T14:02:51Z

dup #194.

warden's primary QA.

#2 - HickupHH3

2022-11-07T00:01:04Z

satisfactory because the warden gave a specific example of centralisation risk of how users can be rugged:

then it is very easy for the user to manipulate parameters in a way to steal funds from normal users e.g. setting a malicious oracle which incorrectly reports a depeg

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