Malt Finance contest - pmerkleplant's results

Yield farmable, incentive-centric algorithmic stable coin.

General Information

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

Malt Finance

Findings Distribution

Researcher Performance

Rank: 22/32

Findings: 3

Award: $484.51

🌟 Selected for report: 3

πŸš€ Solo Findings: 0

Findings Information

Labels

bug
duplicate
2 (Med Risk)

Awards

20.04 USDC - $20.04

External Links

Handle

pmerkleplant

Vulnerability details

Impact

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.

See lines 150 and 175.

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

Findings Information

🌟 Selected for report: pmerkleplant

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

367.919 USDC - $367.92

External Links

Handle

pmerkleplant

Vulnerability details

Impact

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.

See lines 406 and 417.

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

Findings Information

🌟 Selected for report: pmerkleplant

Also found by: 0x0x0x, GiveMeTestEther, WatchPug, pauliax, robee, ye0lde

Labels

bug
G (Gas Optimization)
sponsor confirmed

Awards

4.2914 USDC - $4.29

External Links

Handle

pmerkleplant

Vulnerability details

Impact

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) {

Tools used

grep -rn ".length" .

#0 - GalloDaSballo

2021-12-27T22:19:59Z

Finding is valid, see #136 for a pretty exhaustive explanation

Findings Information

🌟 Selected for report: 0x0x0x

Also found by: defsec, pmerkleplant, ye0lde

Labels

bug
duplicate
G (Gas Optimization)

Awards

10.3016 USDC - $10.30

External Links

Handle

pmerkleplant

Vulnerability details

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");

Tools used

grep -rn "> 0" .

#0 - 0xScotch

2021-12-08T18:27:22Z

#271

#1 - GalloDaSballo

2021-12-31T16:59:16Z

Duplicate of #271

Findings Information

🌟 Selected for report: harleythedog

Also found by: pmerkleplant

Labels

bug
duplicate
G (Gas Optimization)

Awards

25.436 USDC - $25.44

External Links

Handle

pmerkleplant

Vulnerability details

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.

See lines 266 and 389.

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

Findings Information

🌟 Selected for report: pmerkleplant

Labels

bug
G (Gas Optimization)
sponsor confirmed

Awards

56.5245 USDC - $56.52

External Links

Handle

pmerkleplant

Vulnerability details

Impact

Function RewardReinvestor::_bondAccount tries to bond liquidity to an account, even though it is known whether the liquidity is zero.

Proof of Concept

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

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