Malt Finance contest - hyh'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: 9/32

Findings: 5

Award: $2,652.88

🌟 Selected for report: 12

πŸš€ Solo Findings: 1

Findings Information

Labels

bug
duplicate
2 (Med Risk)

Awards

20.04 USDC - $20.04

External Links

Handle

hyh

Vulnerability details

Impact

Liquidity provision can happen at a manipulated price which leads to immediate loss for liquidity provider (i.e. IL happens right after liquidity provision in this case).

This yields direct loss for an account owner, for example (schematically): 0. Suppose tokens A and B has equal value, i.e. market is 1:1 token A:B

  1. Attacker swaps A for B to manipulate it to be 1.5:1, say sides are 150 A and 100 B tokens
  2. LP enters with 150/9 A, 100/9 B, owning 10% of the pool as a result
  3. Attacker swaps back (with profit before fees as after LP entered more funds are needed to move the pool), the pool is back to be around 1:1
  4. If LP leaves now, this will yield (100+x)/10 A, (100+x)/10 B payout, which will be cumulatively less number of equally valued A and B tokens than was deposited in (2)
  5. Attacker profited at the expense of IL loss induced on LP

Proof of Concept

addLiquidity is external without access modifiers, so is meant to be user facing as well as be part of the system. https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DexHandlers/UniswapHandler.sol#L185

Also, it has 5% hard coded slippage: https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DexHandlers/UniswapHandler.sol#L206

The issue here is hard coding the slippage allows front running of the direct usage of the addLiquidity function.

In this case a maxSlippage function argument replacing hard coded 5% level will alleviate the front running issue.

Now:

function addLiquidity() external returns ( ... maltBalance.mul(95).div(100), rewardBalance.mul(95).div(100),

To be:

function addLiquidity(uint256 minAmountUsedBP) external returns ( maltBalance.mul(minAmountUsedBP).div(10000), rewardBalance.mul(minAmountUsedBP).div(10000),

minAmountUsedBP can be set to high enough value, say 9950, when the function be called from RewardReinvestor's provideReinvest and splitReinvest.

#0 - 0xScotch

2021-12-10T00:21:11Z

#219

#1 - GalloDaSballo

2022-01-24T00:49:45Z

Duplicate of #219

Findings Information

🌟 Selected for report: 0x0x0x

Also found by: harleythedog, hyh

Labels

bug
duplicate
2 (Med Risk)

Awards

298.0144 USDC - $298.01

External Links

Handle

hyh

Vulnerability details

Impact

AbstractRewardMine and RewardDistributor both have internal reward token ledger and hold user's tokens in custody. The beneficiaries of these contracts expect to receive particular reward token from them.

An ability to substitute the token when system is fully operational is equivalent to an ability to remove all the funds held with the contracts. A malicious owner can substitute reward token to be any contract, say to be a new token without any value, affectively voiding all the users’ holdings, as all the withdrawals will be carried out in the terms of the new, potentially worthless token.

Also, there is an issue of new vs old token decimals difference. As contracts hold the record of current amounts, it should recalculate them on reward token substitution. Otherwise amounts due could be miscalculated by magnitude of the difference between the decimals.

Proof of Concept

Both contracts have ADMIN_ROLE methods for unconditionally changing contract's reward token.

AbstractRewardMine: https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/AbstractRewardMine.sol#L273

RewardDistributor: https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/RewardSystem/RewardDistributor.sol#L317

RewardDistributor has reward token set during initialization: https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/RewardSystem/RewardDistributor.sol#L62

So it's advised to remove RewardDistributor's setRewardToken method.

AbstractRewardMine has _initialSetup setup function, that isn't called in current implementation, but also does the initialization.

It's advised to remove AbstractRewardMine's setRewardToken method as well.

In order for token to be immutable, the _initialSetup function should implement initializer pattern in some way, i.e. allow only one time setting of the configuration variables.

Or, if other variables need to be changed, it should control token setup directly, for example:

Now:

function _initialSetup(address _rewardToken, address _miningService) internal { _roleSetup(MINING_SERVICE_ROLE, _miningService); _roleSetup(REWARD_MANAGER_ROLE, _miningService); rewardToken = ERC20(_rewardToken); miningService = _miningService; }

To be:

function _initialSetup(address _rewardToken, address _miningService) internal { _roleSetup(MINING_SERVICE_ROLE, _miningService); _roleSetup(REWARD_MANAGER_ROLE, _miningService); if (rewardToken == address(0)) { rewardToken = ERC20(_rewardToken); } miningService = _miningService; }

Although in this case it is advised to change function name as _initialSetup one implies allowing using it only once, i.e. some adherence to the initializer pattern.

If the reward token change is a part of planned setup, an introduction of additional logic is required, for example the contracts need to recalculate future token claims.

#0 - 0xScotch

2021-12-08T12:44:20Z

#285

#1 - GalloDaSballo

2022-01-13T01:07:00Z

Duplicate of #285

Findings Information

🌟 Selected for report: hyh

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

1103.7569 USDC - $1,103.76

External Links

Handle

hyh

Vulnerability details

Impact

BONDING_ROLE cannot be managed after it was initialized.

Proof of Concept

setBonding set the wrong role via _swapRole:

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/MiningService.sol#L116

Set BONDING_ROLE instead of REINVESTOR_ROLE in setBonding function:

Now:

function setBonding(address _bonding) public onlyRole(ADMIN_ROLE, "Must have admin privs") { require(_bonding != address(0), "Cannot use address 0"); _swapRole(_bonding, bonding, REINVESTOR_ROLE); bonding = _bonding; }

To be:

function setBonding(address _bonding) public onlyRole(ADMIN_ROLE, "Must have admin privs") { require(_bonding != address(0), "Cannot use address 0"); _swapRole(_bonding, bonding, BONDING_ROLE); bonding = _bonding; }

#0 - GalloDaSballo

2022-01-23T17:05:02Z

Agree with the finding

Findings Information

🌟 Selected for report: hyh

Also found by: 0x1f8b, WatchPug, cmichel, jayjonah8, tabish

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

36.2087 USDC - $36.21

External Links

Handle

hyh

Vulnerability details

Impact

Being instantiated with wrong configuration, the contract is inoperable and deploy gas costs will be lost. If misconfiguration is noticed too late the various types of malfunctions become possible.

Proof of Concept

The checks for zero addresses during contract construction and initialization are considered to be the best-practice.

Now basically all the contract do not check for correctness of constructor arguments:

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Malt.sol#L29

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/RewardSystem/RewardOverflowPool.sol#L25

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/TransferService.sol#L25

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/ForfeitHandler.sol#L31

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/MiningService.sol#L30

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DexHandlers/UniswapHandler.sol#L47

...

Add zero-address checks and key non-address variables checks in all contract constructors. Small increase of gas costs are far out weighted by wrong deploy costs savings and additional coverage against misconfiguration.

#0 - GalloDaSballo

2022-01-18T14:40:04Z

Input validation is a industry standard finding with low severity

Findings Information

🌟 Selected for report: hyh

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

367.919 USDC - $367.92

External Links

Handle

hyh

Vulnerability details

Impact

A condition requires that calculated retrievable amount shouldn't be too big. If it is the function fails and the remaining portion of commitment is frozen.

As the amount is calculated by the system a user cannot do anything to retrieve remaining part of commitment, if any.

Proof of Concept

claimArbitrage fails if calculated redemption is higher than remaining commitment: https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Auction.sol#L230

userClaimableArbTokens calculated amount can be bigger than remaining user funds: https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/Auction.sol#L279

If the freezing of remainder amount is not intentional then substitute require with ceiling the amount to be retrieved with the remaining part.

Now:

require(redemption <= remaining.add(1), "Cannot claim more tokens than available");

To be:

if (redemption > remaining) { redemption = remaining; }

#0 - GalloDaSballo

2022-01-24T00:48:18Z

The sponsor confirms, personally I'd recommend the sponsor to consider whether this is a good idea as this means that users will renounce the rest of the rewards, which could an unintended consequences of using this recommended mitigation steps.

Findings Information

🌟 Selected for report: hyh

Labels

bug
1 (Low Risk)
sponsor confirmed

Awards

367.919 USDC - $367.92

External Links

Handle

hyh

Vulnerability details

Impact

If Malt token be set to have lower decimals the incentives will be too big to be issued and DAO advance epoch and StabilizerNode auction start functions will fail, the system will have to be redeployed.

For example, if Malt was set to have 6 decimals like USDC, then 100*1e18 StabilizerNode defaultIncentive will be 100 trillions Malt.

Proof of Concept

Now some parts of the system use malt.decimals() (SwingTrader, UniswapHandler), some (StabilizerNode, DAO) use 18.

DAO advanceIncentive:

https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/DAO.sol#L60

StabilizerNode defaultIncentive:

stabilize function https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/StabilizerNode.sol#L145

calls _startAuction in low exchangeRate case, minting defaultIncentive * 10**18 = 100 * 1e18 Malt to the sender as a caller fee. https://github.com/code-423n4/2021-11-malt/blob/main/src/contracts/StabilizerNode.sol#L344

If Malt decimals are meant to be set to 18, add a constant variable and use it across the system to save gas.

If the flexibility is desired malt.decimals() to be used, in a form of contract storage variable for gas optimization (decimals() can be saved to storage once on initialization, and read from there afterwards).

#0 - GalloDaSballo

2022-01-24T00:53:20Z

Because:

  • the sponsor confirmed
  • there are instance in the code where token.decimals() is used (adaptive)

I agree with he finding as it shows that some of the code is not consistently handling the possibility of having different decimals.

I believe the system as presented has no risk, however the warden has shown an improvement that is valid.

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