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: 18/32
Findings: 3
Award: $730.76
π Selected for report: 1
π Solo Findings: 0
0x1f8b
Dao Owner can mint infinite tokens.
In the MaltDAO contract (whose file name is DAO) the initialAdmin can set advanceIncentive without any type of control, which would allow him to set said incentive to an excessive value, call advance and reset said value, in this way he would have created so many Malt tokens as he desired.
According to https://github.com/code-423n4/2021-11-malt/blob/c3a204a2c0f7c653c6c2dda9f4563fd1dc1cecf3/src/scripts/1_local_deploy.ts#L284 said argument is an address, not TimeLock, so it would be something more problematic.
Manual review
Set a range for incentives
#0 - 0xScotch
2021-12-08T14:21:24Z
#190
#1 - GalloDaSballo
2022-01-22T15:07:25Z
Duplicate of #190
π Selected for report: Meta0xNull
Also found by: 0x1f8b
0x1f8b
Detailed description of the impact of this finding.
The buyMalt function is intended to only be called by BUYER_ROLE, this function extracts the balance from rewardToken and performs a swap to send the malt obtained to the sender. On the other hand, the sellMalt function can be invoked by anyone, as long as they have a malt balance, they will perform the reverse step, sell the malt for rewardToken, and send it to the sender. (https://github.com/code-423n4/2021-11-malt/blob/c3a204a2c0f7c653c6c2dda9f4563fd1dc1cecf3/src/contracts/DexHandlers/UniswapHandler.sol#L181)
The problem appears on the line, https://github.com/code-423n4/2021-11-malt/blob/c3a204a2c0f7c653c6c2dda9f4563fd1dc1cecf3/src/contracts/DexHandlers/UniswapHandler.sol#L181, since the sender does not receive the balance swap, he receive the total that the contract has, so if the contract had 1,000,000 DAI pending to be called in buyMalt, with a frontRunning attack, you can send 1 MALT to the contract, and call sellMalt, it would transfer to the attacker the entire balance of DAIs pending purchase.
Manual review
Use rights for sell, or use transferFrom and allowance
#0 - 0xScotch
2021-12-03T13:59:01Z
While this analysis is true there is no reason why there would ever be any tokens in this contract that could be stolen. There is no functionality in the protocol that leaves tokens in the contract. If a user sends funds here and they get stolen via this vector then they just made a silly mistake but it doesn't put the protocol at risk.
The ultimate mitigation is to lock down the other methods in the contract such that this contract is only usable internally.
#1 - 0xScotch
2021-12-10T01:23:44Z
#120
#2 - GalloDaSballo
2022-01-09T22:45:30Z
Duplicate of #120
0x1f8b
Detailed description of the impact of this finding.
It is a common practice in all the revised contracts not to check the inputs of the initialize methods, the addresses are not checked that they are different from address(0)
, some values of ranges are not checked that they are in the range that they have to be , and this can ruin the entire contract deployment because they can only be initialized once.
It is convenient to validate all inputs and constructors or initializers are not exempt from this recommendation.
One example it's:
Manual review
Check inputs in all initialize
#0 - 0xScotch
2021-12-08T14:10:55Z
#299
#1 - GalloDaSballo
2022-01-22T15:03:30Z
Duplicate of #74
π Selected for report: cmichel
Also found by: 0x1f8b, Meta0xNull, WatchPug, defsec, jayjonah8, leastwood, stonesandtrees
0x1f8b
A malicious attacker could take control of the contract and mint from an unwanted account.
As can be seen in the explorer, https://mumbai.polygonscan.com/txs?a=0xee94d4eafe86cb5b3989e655ea26253f9f844c77&p=8 between the deploy and the initialize there is a difference of 46 blocks, which gives plenty of time to send a initialize itself and perform a front running attack.
https://mumbai.polygonscan.com/tx/0xef485fa5829832e6dcec9f431941afd3f2bd07930f3c6a6efd06334170c01053
However, the front running attack in the initialize
is usually easily detected since the owner
variables are overwritten and it can be easily verified that the owner
of the contract has been impersonating, the important thing in this case is that if the pool is listened to and the original values ββare copied, but by adding an entry to the minters
argument, the owner of the contract will see that apparently all the values ββhave been initialized as desired, because the sender is not used anywhere moment, so the attacker will have access to mint as much Malt as he wishes, by being registered with the role of MONETARY_MINTER_ROLE.
Manual review
Set the owner to msg.sender, and check it in every steps
#0 - 0xScotch
2021-12-08T13:03:23Z
#245
#1 - GalloDaSballo
2022-01-22T15:04:00Z
Duplicate of #245
10.3016 USDC - $10.30
0x1f8b
Cheaper storage.
The struct AuctionData file Auction.sol is optimizable. It looks like this:
struct AuctionData { // The full amount of commitments required to return to peg uint256 fullRequirement; // total maximum desired commitments to this auction uint256 maxCommitments; // Quantity of sale currency committed to this auction uint256 commitments; // Malt purchased and burned using current commitments uint256 maltPurchased; // Desired starting price for the auction uint256 startingPrice; // Desired lowest price for the arbitrage token uint256 endingPrice; // Price of arbitrage tokens at conclusion of auction. This is either // when the duration elapses or the maxCommitments is reached uint256 finalPrice; // The peg price for the liquidity pool uint256 pegPrice; // Time when auction started uint256 startingTime; uint256 endingTime; // Is the auction currently accepting commitments? bool active; // The reserve ratio at the start of the auction uint256 preAuctionReserveRatio; // Has this auction been finalized? Meaning any additional stabilizing // has been done bool finalized; // The amount of arb tokens that have been executed and are now claimable uint256 claimableTokens; // The finally calculated realBurnBudget uint256 finalBurnBudget; // The amount of Malt purchased with realBurnBudget uint256 finalPurchased; // A map of all commitments to this auction by specific accounts mapping(address => AccountCommitment) accountCommitments; }
But active
and finalized
, the unique boolean values, should be together, otherwise they will spend two slots instead of one.
uint256 preAuctionReserveRatio; bool active; bool finalized;
Manual review
#0 - GalloDaSballo
2021-12-31T14:31:47Z
Finding is valid, the two bool can fit in a single slot