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: 8/32
Findings: 5
Award: $3,234.62
🌟 Selected for report: 8
🚀 Solo Findings: 2
🌟 Selected for report: Meta0xNull
Meta0xNull
TIMELOCK_ROLE Can Withdraw All FUND from the Contracts via emergencyWithdrawGAS(), emergencyWithdraw(), partialWithdrawGAS(), partialWithdraw().
While I believe developer have good intention to use these functions. It often associate with Rug Pull by developer in the eyes of investors because Rug Pull is not uncommon in Defi. Investors lose all their hard earn money.
Read More: $10.8M Stolen, Developers Implicated in Alleged Smart Contract 'Rug Pull' https://www.coindesk.com/tech/2020/12/02/108m-stolen-developers-implicated-in-alleged-smart-contract-rug-pull/
Read More: The Rise of Cryptocurrency Exit Scams and DeFi Rug Pulls https://www.cylynx.io/blog/the-rise-of-cryptocurrency-exit-scams-and-defi-rug-pulls/
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Permissions.sol#L80-L109
Manual Review
#0 - 0xScotch
2021-12-08T12:47:54Z
#263
#1 - GalloDaSballo
2022-01-08T17:14:36Z
This is not a duplicate of #263, where 263 talks about sidestepping the delay of the timelock, this finding talks about the high degree of power that the TIMELOCK_ROLE has.
This is a typical "admin privilege" finding, it's very important to disclose admin privileges to users so that they can make informed decisions
In this case the TIMELOCK_ROLE can effectively rug the protocol, however this is contingent on the account that has the role to pull the rug.
Because of it's reliance on external factors, am downgrading the finding to medium severity
🌟 Selected for report: thank_you
Also found by: 0x0x0x, Koustre, Meta0xNull, WatchPug, cmichel, defsec, harleythedog, hyh, leastwood, pauliax, pmerkleplant, tabish, xYrYuYx
Meta0xNull
A victim transaction trades a crypto-currency asset X (ex: ETH, DAI, SAI, VERI) to another crypto-asset Y makes a large purchase. A bot sniffs out the transaction and Front-Runs the victim by purchasing asset Y before the large trade is approved. This purchase raises the price of asset-Y for the victim trader and increases the slippage ( Expected price increase or decrease in price based on the volume to be traded and the available liquidity).
Because of this high purchase of asset Y, its price goes up, and Victim buys at a higher price of asset Y, then the attacker sells at a higher price.
Sandwich Attack Explain: https://medium.com/coinmonks/defi-sandwich-attack-explain-776f6f43b2fd
The router.swapExactTokensForTokens: amountOutMin set to 0 mean Sandwich Attack Bot Can Fully Take Advantage of Swing Traders and Stabilizer Node until drain their fund until 0. This is achievable because Swing Traders and Stabilizer Node are trying to ensure Token Price = $1 and thus their behavior are predicable and thus Both Are Very Vulnerable to Sandwich Attack Bot.
No Sandwich Attack Bot: Buy Token @ 0.99 Sell Token @ 1.01
With Sandwich Attack Bot: Buy Token >= 1.01 Sell Token <= 0.99
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DexHandlers/UniswapHandler.sol#L148-L153 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DexHandlers/UniswapHandler.sol#L173-L178
Manual Review
The amountOutMin should not be 0. Swing Traders and Stabilizer Node should be allowed to Set Their Own Slippage But Not Zero.
#0 - 0xScotch
2021-12-10T17:41:03Z
#219
#1 - GalloDaSballo
2022-01-09T23:21:13Z
Duplicate of #219
🌟 Selected for report: Meta0xNull
1103.7569 USDC - $1,103.76
Meta0xNull
There are a lot of different roles in Malt Finance to handle different tasks. All these roles only can set by Admin. If anything happen to Admin and he/she no longer available, the protocol will start countdown to the end of life.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Auction.sol#L890-L1013 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/RewardSystem/RewardDistributor.sol#L291-L345 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Bonding.sol#L327-L355 Many More Sol....
Manual Review
#0 - 0xScotch
2021-12-03T14:03:35Z
This is a known risk that is by design. We will migrate to DAO control of parameters when the protocol matures.
#1 - GalloDaSballo
2022-01-09T23:22:25Z
I respect the wardens work for flagging this up. Because any of these exploits are contingent on the Admin being malicious, I'll downgrade to Medium Severity
🌟 Selected for report: Meta0xNull
Also found by: 0x1f8b
165.5635 USDC - $165.56
Meta0xNull
Is Not Uncommon Normal Users Accidentally Send Tokens into Contract.
ENS Airdrop is a Good Example Normal Users Accidentally Send Tokens into Contract: https://discuss.ens.domains/t/social-amend-airdrop-proposal-to-include-accidentally-returned-funds/6975
In UniswapHandler.sol, sellMalt(), addLiquidity() and removeLiquidity() Have No Access Control. When Normal Users Accidently Deposit Tokens into the Contract, Any Random Persons/Bot Can Withdraw the Tokens because it will safeTransfer to msg.sender who find out there is token balance in the contract.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DexHandlers/UniswapHandler.sol#L185-L219 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DexHandlers/UniswapHandler.sol#L221-L245
Manual Review
Add relevant access control, probably Only StabilizerNode and Admin have Access to this contract various functions like sellMalt(), addLiquidity() and removeLiquidity() etc.
#0 - 0xScotch
2021-12-10T01:25:56Z
Disagree with severity as there is no part of the protocol that would leave funds in this contract. Therefore there is no direct risk of protocol user's funds. This can only lead to lost funds if a random user sends funds directly to this contract.
#1 - GalloDaSballo
2022-01-09T22:44:32Z
As the Sponsor said, the contract won't have any additional funds.
As such there's no particular risk for funds.
Am going to mark this as low because there seems to be an inconsistency (lack of clarity) between the buyMalt
and the sellMalt
, I believe allowing both to be called by anyone is completely fine as the contract is just a utility tool
🌟 Selected for report: Meta0xNull
Also found by: BouSalman
165.5635 USDC - $165.56
Meta0xNull
A wrong user input or wallets defaulting to the zero addresses for a missing input can lead to the contract needing to redeploy or Users'FUND Locked inside the Contract.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Permissions.sol#L63-L77
Manual Review
requires Addresses is not zero.
require(_admin != address(0), "Address Can't Be Zero")
#0 - GalloDaSballo
2022-01-18T14:34:17Z
Lack of address checks is a low severity finding, agree with the finding
🌟 Selected for report: cmichel
Also found by: 0x1f8b, Meta0xNull, WatchPug, defsec, jayjonah8, leastwood, stonesandtrees
Meta0xNull
<code> function initialize( </code> <code> address _timelock,</code> <code> address initialAdmin</code> <code> ) external initializer {</code> <code> _adminSetup(_timelock);</code> <code> _setupRole(ADMIN_ROLE, initialAdmin);</code>
The are multiple contracts using Initializable requires to call the initialize() function with suitable parameters.
However, there is no access control in place to avoid an attacker to front-run this call. As a result of that, an attacker could temporarily block the initialization front-running each deployment, using invalid parameters in the initialize call.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/TransferService.sol#L21-L27 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Malt.sol#L22-L40 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/PoolTransferVerification.sol#L28-L42 More...
Manual Review
Use Constructor Rather Than initialize()
If initialize() Is Unavoidable, then Hardcode an ETH Address in initialize(): require( msg.sender == 0xYOURETHADDRESS );
#0 - 0xScotch
2021-12-08T13:03:10Z
#245
#1 - GalloDaSballo
2022-01-22T15:20:05Z
Duplicate of #245
🌟 Selected for report: Meta0xNull
Also found by: pauliax
165.5635 USDC - $165.56
Meta0xNull
* [WARNING] * ==== * This function should only be called from the constructor when setting * up the initial roles for the system. * * Using this function in any other way is effectively circumventing the admin * system imposed by {AccessControl}. * ==== * * NOTE: This function is deprecated in favor of {_grantRole}.
There are multiple contracts that import Permissions.sol and using Deprecated Function _setupRole() with Security Problem that Applicable to all these contracts because all of the contracts use initialize() Rather Than Constructor.
https://github.com/OpenZeppelin/openzeppelin-contracts/blob/master/contracts/access/AccessControl.sol#L174-L186 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Permissions.sol#L53 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Permissions.sol#L117 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Permissions.sol#L121
Manual Review
Replace _setupRole() with _grantRole()
#0 - GalloDaSballo
2022-01-24T00:36:58Z
Great find by the warden, in lack of any exploitative POC, I agree with low severity
🌟 Selected for report: Meta0xNull
367.919 USDC - $367.92
Meta0xNull
The current ownership transfer process involves the current TIMELOCK_ROLE calling reassignGlobalAdmin(). If the nominated EOA account is not a valid account, it is entirely possible the owner may accidentally transfer ownership to an uncontrolled account, breaking all functions with the TIMELOCK_ROLE modifier.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Permissions.sol#L63-L77
Manual Review
Consider implementing a two step process where the TIMELOCK_ROLE nominates an account and the nominated account needs to call an accept_TIMELOCK_ROLE() function for the transfer of ownership to fully succeed. This ensures the nominated EOA account is a valid and active account.
#0 - GalloDaSballo
2022-01-25T00:54:48Z
Personally I don't believe the "2 step governance change" to be a low severity finding, however the majority of contest rates it as such so we'll allow
🌟 Selected for report: WatchPug
Also found by: GiveMeTestEther, Meta0xNull, robee, ye0lde
Meta0xNull
Shortening revert strings to fit in 32 bytes will decrease deployment time gas and will decrease runtime gas when the revert condition has been met.
Revert strings that are longer than 32 bytes require at least one additional mstore, along with additional overhead for computing memory offset, etc.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/RewardSystem/RewardDistributor.sol#L275 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DAO.sol#L56 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Auction.sol#L223 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Auction.sol#L230 More...
Manual Review
Shorten the revert strings to fit in 32 bytes.
Or consider using Custom Errors (solc >=0.8.4).
#0 - 0xScotch
2021-12-09T22:22:29Z
#317
#1 - GalloDaSballo
2021-12-27T22:14:35Z
Duplicate of #317
🌟 Selected for report: Meta0xNull
56.5245 USDC - $56.52
Meta0xNull
The local variable used as for loop index need not be initialized to 0 because the default value is 0. Avoiding this anti-pattern can save a few opcodes and therefore a tiny bit of gas.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/TransferService.sol#L87 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/AuctionBurnReserveSkew.sol#L54 https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Malt.sol#L34-L37 More...
Manual Review
Remove explicit 0 initialization of for loop index variable.
Before: for (uint i = 0; After for (uint i;
#0 - GalloDaSballo
2022-01-16T15:43:19Z
Assigning zero will add an extra 3 gas cost (MSTORE)
🌟 Selected for report: Meta0xNull
56.5245 USDC - $56.52
Meta0xNull
removeVerifier() loops follows this for-each pattern:
for (uint256 i = 0; i < array.length; i++) {
// do something with array[i]
}
In such for loops, the array.length is read on every iteration, instead of caching it once in a local variable and read it again using the local variable.
https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/TransferService.sol#L87-L88
Manual Review
Read these values from memory once, cache them in local variables and then read them again using the local variables. For example:
Before: for (uint i = 0; i < verifierList.length - 1; i = i + 1) { if (verifierList[i] == _address) {
After: uint256 verifierList_temp = verifierList
for (uint i = 0; i < verifierList_temp.length - 1; i = i + 1) { if (verifierList_temp[i] == _address) {
#0 - GalloDaSballo
2022-01-16T15:42:31Z
Agree with finding, it's best to: -> Cache the contents of the list (avoid multiple HOT SLOADS) -> Cache the length of the list (avoid multiple array boundaries check)