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

Findings: 4

Award: $1,766.82

🌟 Selected for report: 0

🚀 Solo Findings: 0

Awards

14.8433 USDC - $14.84

Labels

bug
duplicate
3 (High Risk)

External Links

Lines of code

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

Vulnerability details

Details & Impact

The require check is performed after exiting the function, meaning that the ETH transfer’s validity check is skipped. This would thus cause invalid withdrawals to be erroneously processed as valid.

Swap the require check and the return statement

require(sent, Errors.VT_COLLATERAL_WITHDRAW_INVALID);
return receivedETHAmount;

#0 - sforman2000

2022-05-18T03:10:25Z

Findings Information

🌟 Selected for report: Picodes

Also found by: hickuphh3

Labels

bug
duplicate
2 (Med Risk)

Awards

1680.7266 USDC - $1,680.73

External Links

Lines of code

https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L124 https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L179-L186 https://github.com/code-423n4/2022-05-sturdy/blob/78f51a7a74ebe8adfd055bdbaedfddc05632566f/smart-contracts/YieldManager.sol#L48 https://github.com/sturdyfi/code4rena-may-2022/blob/main/contracts/protocol/libraries/swap/UniswapAdapter.sol#L41-L76 https://github.com/sturdyfi/code4rena-may-2022/blob/main/contracts/protocol/libraries/swap/UniswapAdapter.sol#L120-L127

Vulnerability details

Details & Impact

All harvested yield tokens are swapped through the Uniswap adapter to USDC. While out of scope, the Uniswap adapter code is relevant here, as I note that the path taken for the swap would be assetFrom -> WETH -> assetTo unless assetFrom is already WETH.

The main issue is the assumption and usage of the 1% fee pool regardless of asset. Those that are familiar with UniV3 will know that there are multiple pool tiers for the same asset pair. Hence, it is possible that there are other pools (Eg. the pool with 0.3% fee) where majority of the liquidity lies instead.

Furthermore, it could be possible that the pool with 1% fee isn’t created. Thus, attempts to swap via the specified path will fail, breaking yield distribution.

Examples

WETH -> USDC

The yield manager swaps yield tokens (CRV, CVX + any additional rewards) to WETH, then to USDC, which is then swapped for other stablecoins if needed. The WETH / USDC 1% fee pool has ~$10.25M liquidity while the 0.05% fee pool has ~$250M liquidity, thus causing yield rewards to lose out due to pool slippage.

poolId 4: SUSD

The SUSD pool (Convex booster pool id 4) distributes SNX as an extra reward. The SNX/ETH 0.3% fee pool has ~$1.78M liquidity while the 1% fee pool has merely ~$36k liquidity (see Uniswap stats).

rKP3R

rKP3R is distributed as an extra reward for a number of pools. There isn’t a rKP3R / ETH pool created.

Enable skipping conversion for some assets, and have a mapping of the pool fee as part of the customisable configuration so that pools with the best liquidity can be used.

#0 - sforman2000

2022-05-18T03:29:37Z

Duplicate of issue #48 (med risk)

#1 - HickupHH3

2022-06-03T07:53:22Z

This isn't an exact duplicate of #48.

There are 2 issues at hand: The first has to do with swapping via non-optimal routes, which is covered by #48. However, the (slightly) more serious issue pertains to do the non-existence of UniV3 pools, because it breaks the yield distribution entirely. Admittedly, it's solvable by creating such a pool, which brings us back to swapping via non-optimal routes.

Hence, I will downgrade my issue to medium severity and mark it as the primary issue because it describes the second case as well.

L-01: _depositYield() doesn’t use SafeERC20 for approvals

L-02: Use safeIncreaseAllowance() instead of safeApprove()

L-03: Verify curveLPToken matches with Convex booster’s convexPoolId

L-04: ConvexCurveLPVault: Ensure 0 msg.value in _depositToYieldPool()

L-05: LidoVault: Restrict ETH sender to WETH contract

NC-01: Spelling errors

#0 - HickupHH3

2022-06-06T08:05:58Z

Context: I was away on holiday when I did this contest and lacked the time to do a proper write-up. Thanks to Dravee for helping me submit hehe

low: L01, L02, L03 L-04 would have been bumped up to a med severity duplicate of #62, but the description is a little too vague to do so. Hence, I will invalidate it. nc: NC01

Awards

26.5129 USDC - $26.51

Labels

bug
G (Gas Optimization)

External Links

G01: ConvexCurveLPVault: Save result of external call

Line References

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

Description

The external call to retrieve the symbol of _lpToken is performed twice. To save gas, save the result of the first call into memory.


string memory lpTokenSymbol = IERC20Detailed(_lpToken).symbol());
SturdyInternalAsset _interalToken = new SturdyInternalAsset(
  string(abi.encodePacked('Sturdy ', lpTokenSymbol),
  string(abi.encodePacked('c', lpTokenSymbol),
  IERC20Detailed(_lpToken).decimals()
);

G02: ConvexCurveLPVault: Save _lpToken.decimals() to avoid external call in pricePerShare()

Line References

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

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

Description

The _internalToken takes on the decimals of the _lpToken. Gas can be saved by replacing the external call to _internalToken for retrieval of the token decimal value in the pricePerShare() function with an SLOAD instead.

uint256 internal decimals;

// in setConfiguration()
decimals = IERC20Detailed(_lpToken).decimals();
SturdyInternalAsset _interalToken = new SturdyInternalAsset(
  string(abi.encodePacked('Sturdy ', IERC20Detailed(_lpToken).symbol())),
  string(abi.encodePacked('c', IERC20Detailed(_lpToken).symbol())),
  decimals
);

function pricePerShare() external view override returns (uint256) {
  return 10**decimals;
}
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