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
Rank: 7/65
Findings: 4
Award: $1,766.82
🌟 Selected for report: 0
🚀 Solo Findings: 0
🌟 Selected for report: pedroais
Also found by: 0x4non, 0x52, 0xf15ers, 0xliumin, CertoraInc, Dravee, GimelSec, IllIllI, MaratCerby, StErMi, TerrierLover, WatchPug, berndartmueller, cccz, dipp, fatherOfBlocks, hake, hickuphh3, hyh, isamjay, mtz, oyc_109, p4st13r4, peritoflores, rotcivegaf, saian, simon135, sorrynotsorry, sseefried, tabish, z3s
14.8433 USDC - $14.84
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
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
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.
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.
🌟 Selected for report: IllIllI
Also found by: 0x1f8b, 0x4non, 0xNazgul, 0xf15ers, 0xkatana, 0xliumin, AlleyCat, BouSalman, Dravee, Funen, GimelSec, Hawkeye, MaratCerby, Picodes, StErMi, TerrierLover, WatchPug, Waze, berndartmueller, bobirichman, cryptphi, csanuragjain, defsec, delfin454000, dipp, fatherOfBlocks, hake, hickuphh3, hyh, joestakey, kebabsec, mics, mtz, oyc_109, p4st13r4, p_crypt0, robee, rotcivegaf, sikorico, simon135, sorrynotsorry, tintin
44.7404 USDC - $44.74
#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
🌟 Selected for report: IllIllI
Also found by: 0v3rf10w, 0x1f8b, 0x4non, 0xNazgul, 0xf15ers, 0xkatana, 0xliumin, Cityscape, Dravee, Fitraldys, Funen, GimelSec, Hawkeye, JC, MaratCerby, SooYa, StErMi, Tomio, WatchPug, Waze, bobirichman, defsec, delfin454000, fatherOfBlocks, hake, hansfriese, hickuphh3, ignacio, joestakey, kebabsec, mics, mtz, oyc_109, robee, rotcivegaf, samruna, sikorico, simon135, z3s
26.5129 USDC - $26.51
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() );
_lpToken.decimals()
to avoid external call in pricePerShare()
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; }