Sturdy contest - berndartmueller'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: 2/65

Findings: 5

Award: $3,253.78

🌟 Selected for report: 1

πŸš€ Solo Findings: 0

Findings Information

🌟 Selected for report: jonah1005

Also found by: Picodes, WatchPug, berndartmueller, sorrynotsorry

Labels

bug
duplicate
3 (High Risk)
disagree with severity

Awards

1225.2497 USDC - $1,225.25

External Links

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L125

Vulnerability details

Impact

When withdrawing ETH collateral from LidoVault with withdrawCollateral(), stETH is exchanged to ETH via Curve while using a slippage value of 2% (200, L136).

The resulting exchanged amount receivedETHAmount is then validated to be larger than the requested withdrawal _amount minus 1% slippage:

require(withdrawAmount >= _amount.percentMul(99_00), Errors.VT_WITHDRAW_AMOUNT_MISMATCH);

If the curve stETH pool is unbalanced and therefore causes a slippage of ~2%, the GeneralVault.withdrawCollateral call will revert.

Proof of Concept

GeneralVault.sol#L125

Tools Used

Manual review

Make sure to use the same value for slippage checks within GeneralVault and LidoVault.

#0 - sforman2000

2022-05-18T02:28:53Z

Awards

14.8433 USDC - $14.84

Labels

bug
duplicate
3 (High Risk)
disagree with severity

External Links

Lines of code

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

Vulnerability details

Impact

The LidoVault contract uses a low level Solidity .call() to send ETH to the user, but the require to check the sent return value is placed after the return, hence the sent success return value is not checked. This could cause a silent fail transferring ETH and loss of funds for the user.

Proof of Concept

LidoVault.sol#L141-L142

// send ETH to user
(bool sent, bytes memory data) = address(_to).call{value: receivedETHAmount}('');
return receivedETHAmount;
require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID); // @audit-info unreachable code

Tools Used

Manual review

Move the require check before the return.

#0 - sforman2000

2022-05-18T01:31:40Z

Findings Information

🌟 Selected for report: rotcivegaf

Also found by: AuditsAreUS, MaratCerby, StErMi, berndartmueller, cccz, dipp

Labels

bug
duplicate
2 (Med Risk)

Awards

283.5578 USDC - $283.56

External Links

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/ConvexCurveLPVault.sol#L131

Vulnerability details

Impact

If a borrower deposits Curve LP tokens into the ConvexCurveLPVault contract via the payable function GeneralVault.depositCollateral(address _asset, uint256 _amount) and accidentally sends a non-zero Ether value with it, then the Ether value sent will be locked within the contract without the ability to retrieve it.

Proof of Concept

ConvexCurveLPVault._depositToYieldPool

Tools Used

Manual review

Make sure the function ConvexCurveLPVault._depositToYieldPool reverts if msg.value > 0.

#0 - sforman2000

2022-05-18T02:26:47Z

Findings Information

🌟 Selected for report: berndartmueller

Also found by: WatchPug

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

1680.7266 USDC - $1,680.73

External Links

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/GeneralVault.sol#L121-L124

Vulnerability details

Impact

Withdrawing ETH collateral via the withdrawCollateral function using type(uint256).max for the _amount parameter reverts the transaction due to _asset being the zero-address and IERC20Detailed(_asset).decimals() not working for native ETH.

Proof of Concept

GeneralVault.sol#L121-L124

if (_amount == type(uint256).max) {
    uint256 decimal = IERC20Detailed(_asset).decimals(); // @audit-info does not work for native ETH. Transaction reverts
    _amount = _amountToWithdraw.mul(this.pricePerShare()).div(10**decimal);
}

Tools Used

Manual review

Check _asset and use hard coded decimal value (18) for native ETH

#0 - HickupHH3

2022-06-03T04:33:15Z

Good find! Stated in _asset description that null address is interpreted as ETH, which isn't a token, and therefore reverts when attempting to fetch its decimals.

QA Report

Table of Contents

Low Risk

[L-01]: Events not emitted for important state changes

Description

When changing state variables events are not emitted. Emitting events allows monitoring activities with off-chain monitoring tools.

Findings

YieldManager.setExchangeToken()
YieldManager.registerAsset()
YieldManager.setCurvePool()
ConvexCurveLPVault.setConfiguration()
CollateralAdapter.addCollateralAsset()

Emit events for state variable changes.

[L-02]: Zero-address checks are missing

Description

Zero-address checks are a best-practice for input validation of critical address parameters. While the codebase applies this to most most cases, there are many places where this is missing in constructors and setters.

Impact: Accidental use of zero-addresses may result in exceptions, burn fees/tokens or force redeployment of contracts.

Findings

YieldManager.sol#L74

_assetsList[_assetsCount] = _asset;

ConvexCurveLPVault.sol#L41

curveLPToken = _lpToken;

Add zero-address checks, e.g.:

require(_asset != address(0), "Zero-address");

[L-03]: Restrict ETH funds receivable by LidoVault to be only from Curve exchange

Description

Native ETH fund transfers into the LidoVault contract are only expected from the curveExchange contract. Hence, it would be good to restrict incoming ETH fund transfers to prevent accidental native fund transfers from other sources.

Findings

LidoVault.sol#L24

/**
  * @dev Receive Ether
  */
receive() external payable {}

Modify the receive() function to only accept transfers from the wrapped token contract:

receive() external payable {
  require(msg.sender == address(curveExchange), 'Only curve exchange');
}

#0 - HickupHH3

2022-06-06T05:11:02Z

Low: L03 NC: L01, L02

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