Platform: Code4rena
Start Date: 25/11/2021
Pot Size: $80,000 USDC
Total HM: 35
Participants: 32
Period: 7 days
Judge: GalloDaSballo
Total Solo HM: 27
Id: 59
League: ETH
Rank: 22/32
Findings: 3
Award: $484.51
π Selected for report: 3
π Solo Findings: 0
π Selected for report: thank_you
Also found by: 0x0x0x, Koustre, Meta0xNull, WatchPug, cmichel, defsec, harleythedog, hyh, leastwood, pauliax, pmerkleplant, tabish, xYrYuYx
pmerkleplant
The functions UniswapHandler::sellMalt()
and UniswapHandler::buyMalt()
are missing slippage checks which can lead to being vulnerable to sandwich
attacks.
A common attack in DeFi is the sandwich attack. Upon observing a trade of asset X for asset Y, an attacker frontruns the victim trade by also buying asset Y, lets the victim execute the trade, and then backruns (executes after) the victim by trading back the amount gained in the first trade. Intuitively, one uses the knowledge that someoneβs going to buy an asset, and that this trade will increase its price, to make a profit. The attackerβs plan is to buy this asset cheap, let the victim buy at an increased price, and then sell the received amount again at a higher price afterwards.
Such an attack would leak value from the protocol.
Calculate the expected amount to receive from the trade beforehand and adjust
this amount with some acceptable slippage.
Functionality to achieve this is included in UniswapV2Library.sol
.
Otherwise, if feasible, don't send transactions that include trades through the public mempool, e.g. use the Flashbots network.
#0 - 0xScotch
2021-12-10T00:21:41Z
#219
#1 - GalloDaSballo
2022-01-26T13:28:56Z
Duplicate of #219
π Selected for report: pmerkleplant
367.919 USDC - $367.92
pmerkleplant
The functions setDefaultIncentive
and setExpansionDamping
in StabilizerNode.sol
require their arguments to be non-zero, i.e. to be positive, as their argument
types are uint
.
However, the error messages state that the arguments should not be non-negative.
Change the error messages to something like: "Must be above 0".
#0 - GalloDaSballo
2022-01-18T14:54:54Z
Agree with the finding, while there's no risk of exploit the revert messages are incorrect
π Selected for report: pmerkleplant
Also found by: 0x0x0x, GiveMeTestEther, WatchPug, pauliax, robee, ye0lde
4.2914 USDC - $4.29
pmerkleplant
Caching the array length in a for
-loop saves gas as the length does not need
to be read on every iteration.
The following loops could be refactored:
./Malt.sol:34: for (uint256 i = 0; i < minters.length; i = i + 1) { ./Malt.sol:37: for (uint256 i = 0; i < burners.length; i = i + 1) { ./TransferService.sol:87: for (uint i = 0; i < verifierList.length - 1; i = i + 1) { ./Auction.sol:407: for (uint i = 0; i < epochCommitments.length; ++i) { ./libraries/UniswapV2Library.sol:66: for (uint i; i < path.length - 1; i++) { ./AuctionParticipant.sol:107: for (uint256 i = replenishingIndex; i < auctionIds.length; i = i + 1) { ./MiningService.sol:49: for (uint i = 0; i < mines.length; i = i + 1) { ./MiningService.sol:69: for (uint i = 0; i < mines.length; i = i + 1) { ./MiningService.sol:86: for (uint i = 0; i < mines.length; i = i + 1) { ./MiningService.sol:96: for (uint i = 0; i < mines.length; i = i + 1) { ./MiningService.sol:142: for (uint i = 0; i < mines.length - 1; i = i + 1) { ./MiningService.sol:166: for (uint i = 0; i < mines.length; i = i + 1) { ./DexHandlers/UniswapHandler.sol:317: for (uint i = 0; i < buyers.length - 1; i = i + 1) {
grep -rn ".length" .
#0 - GalloDaSballo
2021-12-27T22:19:59Z
Finding is valid, see #136 for a pretty exhaustive explanation
π Selected for report: 0x0x0x
Also found by: defsec, pmerkleplant, ye0lde
10.3016 USDC - $10.30
pmerkleplant
When dealing with unsigned integer types, comparisons with != 0
is cheaper
than > 0
.
The following statements could be refactored:
./MovingAverage.sol:387: if (oldSample.timestamp > 0 ... ./MovingAverage.sol:414: require(_sampleLength > 0, "Cannot have 0 second sample length"); ./MovingAverage.sol:430: require(_sampleMemory > 0, "Cannot have sample memroy of 0"); ./AuctionBurnReserveSkew.sol:109: if (aggregate.maxCommitments > 0) { ./AuctionBurnReserveSkew.sol:190: require(_lookback > 0, "Cannot have zero lookback period"); ./AuctionPool.sol:118: if (globalRewarded > 0 && userReward > 0) { ./AuctionPool.sol:125: if (forfeitAmount > 0) { ./AuctionPool.sol:129: if (declaredRewardDecrease > 0) { ./AuctionPool.sol:141: if (forfeitedRewards > 0) { ./MaltDataLab.sol:236: require(_price > 0, "Cannot have 0 price"); ./MaltDataLab.sol:244: require(_lookback > 0, "Cannot have 0 lookback"); ./MaltDataLab.sol:252: require(_lookback > 0, "Cannot have 0 lookback"); ./MaltDataLab.sol:260: require(_lookback > 0, "Cannot have 0 lookback"); ./ImpliedCollateralService.sol:64: if (maxAmount > 0) { ./ImpliedCollateralService.sol:68: if (maxAmount > 0) { ./SwingTrader.sol:138: if (profit > 0) { ./Auction.sol:221: require(amountTokens > 0, "No claimable Arb tokens"); ./Auction.sol:267: return auction.endingTime > 0 && (now >= auction.endingTime || auction.finalPrice > 0 || auction.commitments >= auction.maxCommitments); ./Auction.sol:387: return auction.startingTime > 0; ./Auction.sol:641: if (realBurnBudget > 0) { ./Auction.sol:661: require(auction.startingTime > 0, "No auction available for the given id"); ./Auction.sol:665: if (auction.maltPurchased > 0) { ./Auction.sol:863: if (auction.commitments > 0 || !auction.finalized) { ./Auction.sol:896: require(_length > 0, "Length must be larger than 0"); ./Auction.sol:974: require(_split > 0 && _split <= 10000, "Must be between 0-100%"); ./Auction.sol:982: require(_maxEnd > 0 && _maxEnd <= 1000, "Must be between 0-100%"); ./Auction.sol:990: require(_lookback > 0, "Must be above 0"); ./Auction.sol:998: require(_lookback > 0, "Must be above 0"); ./Auction.sol:1006: require(_bps > 0 && _bps < 1000, "Must be between 0-100%"); ./Auction.sol:1014: require(_threshold > 0, "Must be between greater than 0"); ./Bonding.sol:87: require(amount > 0, "Cannot bond 0"); ./Bonding.sol:97: require(amount > 0, "Cannot unbond 0"); ./Bonding.sol:101: require(bondedBalance > 0, "< bonded balance"); ./Bonding.sol:117: require(amount > 0, "Cannot unbond 0"); ./Bonding.sol:121: require(bondedBalance > 0, "< bonded balance"); ./Bonding.sol:283: if (diff > 0) { ./StabilizerNode.sol:273: if (callerCut > 0) { ./StabilizerNode.sol:277: if (auctionPoolCut > 0) { ./StabilizerNode.sol:281: if (swingTraderCut > 0) { ./StabilizerNode.sol:285: if (treasuryCut > 0) { ./StabilizerNode.sol:289: if (daoCut > 0) { ./StabilizerNode.sol:293: if (lpCut > 0) { ./StabilizerNode.sol:362: require(_period > 0, "Must be greater than 0"); ./StabilizerNode.sol:410: require(_incentive > 0, "No negative incentive"); ./StabilizerNode.sol:422: require(amount > 0, "No negative damping"); ./StabilizerNode.sol:454: require(_upper > 0 && _lower > 0, "Must be above 0"); ./StabilizerNode.sol:493: require(_maxContribution > 0 && _maxContribution <= 100, "Must be between 0 and 100"); ./StabilizerNode.sol:557: require(_period > 0, "Cannot have 0 period"); ./StabilizerNode.sol:566: require(_distance > 0 && _distance < 1000, "Override must be between 0-100%"); ./StabilizerNode.sol:575: require(_period > 0, "Cannot have 0 period"); ./PoolTransferVerification.sol:76: require(newThreshold > 0 && newThreshold < 10000, "Threshold must be between 0-100%"); ./PoolTransferVerification.sol:85: require(lookback > 0, "Cannot have 0 lookback"); ./RewardReinvestor.sol:93: require(rewardLiquidity > 0, "Cannot reinvest 0"); ./RewardReinvestor.sol:116: if (maltBalance > 0) { ./RewardReinvestor.sol:120: if (rewardTokenBalance > 0) { ./DAO.sol:47: if (offeringMint > 0) { ./DAO.sol:78: require(amount > 0, "Cannot have zero amount"); ./DAO.sol:94: require(_length > 0, "Cannot have zero length epochs"); ./ERC20VestedMine.sol:93: if (globalRewarded > 0 && userReward > 0) { ./ERC20VestedMine.sol:115: if (forfeitReward > 0) { ./ERC20VestedMine.sol:119: if (declaredRewardDecrease > 0) { ./LiquidityExtension.sol:163: require(_ratio > 0 && _ratio <= 100, "Must be between 0 and 100"); ./AbstractRewardMine.sol:149: if (rewardDenominator > 0) { ./ForfeitHandler.sol:49: if (swingTraderCut > 0) { ./ForfeitHandler.sol:53: if (treasuryCut > 0) { ./AuctionEscapeHatch.sol:192: require(amount > 0, "Nothing to claim"); ./AuctionEscapeHatch.sol:223: require(_earlyExitBps > 0 && _earlyExitBps <= 1000, "Must be between 0-100%"); ./AuctionEscapeHatch.sol:231: require(_period > 0, "Cannot have 0 lookback period"); ./RewardSystem/RewardDistributor.sol:146: require(reward > 0, "Cannot declare 0 reward"); ./RewardSystem/RewardDistributor.sol:268: if (amount > 0) { ./RewardSystem/RewardDistributor.sol:279: if (amount > 0) { ./RewardSystem/RewardThrottle.sol:85: if (aprTarget > 0 && _epochAprGivenReward(epoch, balance) > aprTarget) { ./RewardSystem/RewardThrottle.sol:89: if (remainder > 0) { ./RewardSystem/RewardThrottle.sol:271: if (underflow > 0) { ./RewardSystem/RewardThrottle.sol:323: require(_smoothingPeriod > 0, "No zero smoothing period"); ./RewardSystem/RewardOverflowPool.sol:76: require(_maxFulfillment > 0, "Can't have 0 max fulfillment");
grep -rn "> 0" .
#0 - 0xScotch
2021-12-08T18:27:22Z
#271
#1 - GalloDaSballo
2021-12-31T16:59:16Z
Duplicate of #271
π Selected for report: harleythedog
Also found by: pmerkleplant
pmerkleplant
The variable treasuryRewardCut
in StabilizerNode.sol
is unnecessary because
the treasury's reward is calculated as the rest of the rewards after each
other entity got their reward cut.
Removing the unnecessary variable decreases the deployed bytecode and saves gas.
#0 - 0xScotch
2021-12-09T23:07:01Z
#379
#1 - GalloDaSballo
2021-12-31T17:04:14Z
Duplicate of #379
π Selected for report: pmerkleplant
56.5245 USDC - $56.52
pmerkleplant
Function RewardReinvestor::_bondAccount
tries to bond liquidity to an account,
even though it is known whether the liquidity is zero.
The return value liquidityCreated
in line 105 can be zero.
The following function call, bondToAccount()
, then reverts with "Cannot bond 0".
Gas could be saved if the function would revert earlier, i.e. in line 106,
if the liquidityCreated
is zero.
#0 - GalloDaSballo
2021-12-31T17:07:02Z
In principle I agree with the finding, failing early would save the cost of the approve and the CALL In practice the refactoring may be more complex than necessary, so up to the sponsor to actually implement the improvement