Sturdy contest - mtz's results

The first protocol for interest-free borrowing and high yield lending.

General Information

Platform: Code4rena

Start Date: 13/05/2022

Pot Size: $30,000 USDC

Total HM: 8

Participants: 65

Period: 3 days

Judge: hickuphh3

Total Solo HM: 1

Id: 125

League: ETH

Sturdy

Findings Distribution

Researcher Performance

Rank: 12/65

Findings: 4

Award: $460.58

🌟 Selected for report: 1

🚀 Solo Findings: 0

Awards

14.8433 USDC - $14.84

Labels

bug
duplicate
3 (High Risk)
upgraded by judge

External Links

Judge has assessed an item in Issue #63 as High risk. The relevant finding follows:

#0 - HickupHH3

2022-06-06T04:05:26Z

ETH can accidentally be sent to a contract that rejects the transfer because the sent value returned by address(_to).call{value: receivedETHAmount}(''); is not actually checked. https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L140-L142 On line 140 a call is made to transfer ETH (bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}(''); Then on line 141 receivedETHAmount is returned. And on line 142 the sending of ETH is checked.

Recommended Mitigation Steps To fix this, line 141 and line 142 should be swapped.

#1 - HickupHH3

2022-06-06T04:06:24Z

Duplicate of #157

Findings Information

🌟 Selected for report: mtz

Also found by: 0x52, hyh, jonah1005, leastwood, sorrynotsorry

Labels

bug
2 (Med Risk)
sponsor acknowledged

Awards

367.5749 USDC - $367.57

External Links

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L129-L134 https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L160-L161

Vulnerability details

Impact

An attacker can use MEV (via gas auction or Flashbots or control of miners) to cause an unfair division of yield. By providing a very large (relative to the size of all other stablecoin deposits combined) stablecoin deposit Just-in-Time before an admin's call to distributeYield the stablecoin deposited by the attacker will receive a very large amount of the yield and the attacker can immediately withdraw their deposit after yield is distributed. We assume this allows an attacker to get a lot of the yield reward even though they haven't provided any deposit that has been borrowed. However, the exact mechanism for how yield is distributed to lenders of a particular stablecoin is in LendingPool.sol, which is out of scope. However it is implied in the documentation of this repo that it is based on the balance of that asset the lender has provided. We have confirmed that in LendingPool.sol the yield is distributed based on the proportion of the asset provided. However, even ignoring this, MEV can still be used to unfairly hurt lenders of other stablecoins.

Proof of Concept

Provide direct links to all referenced code in GitHub. Add screenshots, logs, or any other relevant proof that illustrates the concept.

Proof of Concept

  1. An attacker watches the mempool for calls to distrbitueYield by the admin.
  2. The attacker orders the block's transactions (most easily using a flashbots bundle) in the following order: i. Attacker deposits stablecoins to lend (ideally the stablecoin will be the one with the least volume). ii. admin's call to distributeYield happens. iii. Attacker withdraws their deposit.

The attacker has thus made the asset they deposited (and thus themselves) receive much of the yield even though they provide no value to Sturdy since none of their deposit is ever borrowed so the never do anything to earn yield for sturdy. This attack can be done by a whale or by borrowing (even from sturdy) assets and converting them to a stablecoins accepted by sturdy before i. and returning them after iii. This will essentially be cost free for the attacker, none of their capital will ever be tied up by borrowers.

The simplest way to mitigate this is for the admin to use flashbots or some other means of submitting the distributeYield call that skips the mempool. This is only a partial mitigation since attackers can still withdraw right after yield is distributed and get lucky by depositing soon before the distribution thus still capture more yield than they should have. A better mitigation could use something like snapshotting who has deposited since the last yield distribution and only give these depositers yield based on the size of their deposits the next time yield is distributed.

#0 - sforman2000

2022-05-18T04:12:45Z

We will use flashbots and vary when/how often yield is harvested to mitigate this.

#1 - HickupHH3

2022-06-03T07:38:13Z

I take reference to discussions on Discord and in a thread below: https://github.com/code-423n4/2022-03-biconomy-findings/issues/135

To quote from 0xleastwood: "Protocol leaked value in has a broad context but I think most judges can agree that it would pertain to rewards being paid out a lower rate than expected. Or, users can extract small amounts (up to debate on what is considered to be small) from the protocol under certain assumptions."

Hence, as per the TLDR risk assessment: 2 — Med: Assets not at direct risk, but the function of the protocol or its availability could be impacted, or leak value with a hypothetical attack path with stated assumptions, but external requirements.

I would downgrade this to a medium severity.

ETH can accidentally be sent to a contract that rejects the transfer because the sent value returned by address(_to).call{value: receivedETHAmount}(''); is not actually checked.

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L140-L142 On line 140 a call is made to transfer ETH (bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}(''); Then on line 141 receivedETHAmount is returned. And on line 142 the sending of ETH is checked.

To fix this, line 141 and line 142 should be swapped.

ETH can accidentally be withdrawn to the 0x0 address.

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/LidoVault.sol#L122-L142

Neither _withdrawFromYieldPool nor withdrawCollateral validates that _to is not the 0x0 address. They should. Unlike token transfers which typically reject transfers to the 0x0 address, call on the 0x0 address will not fail if the caller has the amount of ETH they send (note this is different from the last issue reported in the QA report, which is regarding contracts only).

Incorrect comment about vault fee.

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/GeneralVault.sol#L51 It is not 20%, it is limited to less than or equal to 30%

Typo in comment

https://github.com/code-423n4/2022-05-sturdy/blob/main/smart-contracts/YieldManager.sol#L217

@param _amount The mount of asset should read @param _amount The amount of asset. amount is spelled incorrectly.

Admin can deceptively increase fees before processing yield

A malicious admin can call setTreasuryInfo immediately before calling processYield to increase the vault fee and then set the vault fee back to the advertised value by calling setTreasuryInfo immediately after. Instead setTreasuryInfo should only be allowed to set the vault fee once. After this, the vault fee should be set when calling processYield for the next time processYield is called.

#0 - HickupHH3

2022-06-06T04:08:51Z

Low issues: Vault fee comment, deceptively increase fees NC issues: zero address checks, spelling Upgraded: first issue.

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