Backd Tokenomics contest - Picodes's results

Maximize the power of your assets and start earning yield

General Information

Platform: Code4rena

Start Date: 27/05/2022

Pot Size: $75,000 USDC

Total HM: 20

Participants: 58

Period: 7 days

Judge: GalloDaSballo

Total Solo HM: 15

Id: 131

League: ETH

Backd

Findings Distribution

Researcher Performance

Rank: 3/58

Findings: 4

Award: $7,181.24

๐ŸŒŸ Selected for report: 2

๐Ÿš€ Solo Findings: 1

Findings Information

๐ŸŒŸ Selected for report: Picodes

Also found by: scaraven

Labels

bug
3 (High Risk)
disagree with severity
sponsor confirmed

Awards

4098.7998 USDC - $4,098.80

External Links

Lines of code

https://github.com/code-423n4/2022-04-backd/blob/c856714a50437cb33240a5964b63687c9876275b/backd/contracts/tokenomics/Minter.sol#L181

Vulnerability details

Impact

The actual total supply of the token is random and depends on when _executeInflationRateUpdate is executed.

Proof of concept

The README and tokenomic documentation clearly states that โ€œThe token supply is limited to a total ofย 268435456ย tokens.โ€. However when executing _executeInflationRateUpdate, it first uses the current inflation rate to update the total available before checking if it needs to be reduced.

Therefore if no one mints or calls executeInflationRateUpdate for some time around the decay point, the inflation will be updated using the previous rate so the totalAvailableToNow will grow too much.

Mitigation steps

You should do

totalAvailableToNow += (currentTotalInflation * (block.timestamp - lastEvent));

Only if the condition block.timestamp >= lastInflationDecay + _INFLATION_DECAY_PERIOD is false.

Otherwise you should do

totalAvailableToNow += (currentTotalInflation * (lastInflationDecay + _INFLATION_DECAY_PERIOD - lastEvent));โ€จ

Then update the rates, then complete with

totalAvailableToNow += (currentTotalInflation * (block.timestamp - lastInflationDecay + _INFLATION_DECAY_PERIOD));โ€จ

Note that as all these variables are either constants either already loaded in memory this is super cheap to do.

#0 - danhper

2022-06-06T15:18:32Z

I believe this should actually be high severity

#1 - GalloDaSballo

2022-06-19T16:55:10Z

The warden has identified the lack of an upper bound on the inflation math which would make it so that more than the expected supply cap of the token could be minted.

The sponsor agree that this should be of High Severity.

Because this breaks the protocol stated invariant of a specific cap of 268435456 tokens, I agree with High Severity

Findings Information

๐ŸŒŸ Selected for report: Picodes

Labels

bug
2 (Med Risk)
disagree with severity
sponsor confirmed

Awards

2732.5332 USDC - $2,732.53

External Links

Lines of code

https://github.com/code-423n4/2022-05-backd/blob/2a5664d35cde5b036074edef3c1369b984d10010/protocol/contracts/tokenomics/Minter.sol#L220 https://github.com/code-423n4/2022-04-backd/blob/c856714a50437cb33240a5964b63687c9876275b/backd/contracts/tokenomics/InflationManager.sol#L559 https://github.com/code-423n4/2022-04-backd/blob/c856714a50437cb33240a5964b63687c9876275b/backd/contracts/tokenomics/InflationManager.sol#L572 https://github.com/code-423n4/2022-04-backd/blob/c856714a50437cb33240a5964b63687c9876275b/backd/contracts/tokenomics/InflationManager.sol#L586

Vulnerability details

Impact

When updating pool inflation rates, other pools see their currentRate being modified without having poolCheckpoint called, which leads to false computations.

This will lead to either users losing a part of their claims, but can also lead to too many tokens could be distributed, preventing some users from claiming due to the totalAvailableToNow requirement in Minter.

Proof of concept

Imagine you have 2 AMM pools A and B, both with an ammPoolWeight of 100, where poolCheckpoint has not been called for a moment. Then, imagine calling executeAmmTokenWeight to reduce the weight of A to 0.

Only A is checkpointed here, so when B will be checkpointed it will call getAmmRateForToken, which will see a pool weight of 100 and a total weight of 100 over the whole period since the last checkpoint of B, which is false, therefore it will distribute too many tokens. This is critical has the minter expects an exact or lower than expected distribution due to the requirement of totalAvailableToNow.

In the opposite direction, when increasing weights, it will lead to less tokens being distributed in some pools than planned, leading to a loss for users.

Mitigation steps

Checkpoint every LpStakerVault, KeeperGauge or AmmGauge when updating the weights of one of them.

#0 - chase-manning

2022-06-07T13:13:28Z

We think this should be Medium as impact is quite minor

#1 - GalloDaSballo

2022-06-19T15:39:39Z

Because getAmmRateForToken uses the totalAmmTokenWeight, then all Gauges will need to be poolCheckpointed at the time the weight is changed-

This is because for systems that accrue points over time, any time the multiplier changes, the growth of points has changed, and as such a new accrual needs to be performed.

I believe there's 3 ways to judge this finding:

  • It's Medium as the Admin is using their privilege to change the points, potentially to their favour or to someones detriment
  • It's High because the math is wrong and the code fails (by a way of lack of approximation) to properly allocate the resources
  • It's Medium because while the finding is correct in a vacuum, anyone can call poolCheckpoint, as such if a true concern for fairness is made, anyone can front-run and back-run the update of weights with the goal of offering the most accurate math possible

Given those considerations, I want to emphasize that not calling poolCheckpoint can cause potentially drastic leaks of value, in a way similar to the original Sushi MasterChef's lack of massUpdatePools could.

That said, I believe the finding is of medium severity as any individual could setup a bot to monitor and prevent this and it would require the weights to be changed by governance and impact can be quite minor as long as the pools are used

Awards

119.8232 USDC - $119.82

Labels

bug
QA (Quality Assurance)
resolved
sponsor confirmed

External Links

[Non-critical - 01] - In Minter, currentInflationAmountLp, currentInflationAmountKeeper, currentInflationAmountAmm could be made internal.

There are getters like getLpInflationRate for these variables, so there is no need to make them public as it just creates duplicates view functions.

#0 - GalloDaSballo

2022-06-20T00:29:56Z

Agree with the advice, nice finding, wish the warden sent more

Awards

230.0919 USDC - $230.09

Labels

bug
G (Gas Optimization)
resolved
sponsor confirmed

External Links

[Gas - 01] - Gauges could mint directly to Minter

Currently all mint action pass through the InflationManager which checks the access control using the mapping gauges and then forward the call to the Minter: see this code. It would be way more gas efficient considering the frequency of mints versus gauges mapping updates to maintain identical mappings in InflationManager and Minter and to mint directly to Minter.

This would just imply using an internal function in InflationManager to make sure each time gauges is updated it is also updated in Minter.

[Gas - 02] - In Minter, token could be made immutable

In Minter, token is not immutable but can only be set once. I assume this is for simplicity as you want to deploy Minter, BkdToken and then set the address of BkdToken in Minter. But this overhead could easily be avoided by pre-computing the deployment address of BkdToken so it could be set in the constructor of Minter and be immutable. This would save a lot of gas during the whole contract lifecycle.

To precompute deployment addresses, you can use the CREATE2 opcode: check https://docs.openzeppelin.com/cli/2.8/deploying-with-create2 or https://medium.com/coinmonks/pre-compute-contract-deployment-address-using-create2-8c01e80ab7da.

This also applies to other places in the code like https://github.com/code-423n4/2022-04-backd/blob/c856714a50437cb33240a5964b63687c9876275b/backd/contracts/tokenomics/InflationManager.sol#L56

[Gas - 03] - In Minter, currentInflationAmountLp, currentInflationAmountKeeper, currentInflationAmountAmm could be made internal.

There are getters like getLpInflationRate for these variables, so there is no need to make them public as it just creates duplicates view functions.

[Gas - 04] - Minter and BkdToken could be merged

To save expensive external calls, why not merging Minter and BkdToken into a single contract ? I think it doable from a contract size point of view, and references to one another are immutable so it would totally make sense to merge them.

[Gas - 05] In VestedEscrow, totalTime could be made immutable.

totalTime is not changed during the contract lifetime so could be made immutable !

[Gas - 06] In Minter, there is no need to inherit ReentrancyGuard

In Minter the keyword nonReentrant is used only once here, but it is useless as there is only an external call to a trusted contract: the token, and no crucial states updates after this call.

#0 - GalloDaSballo

2022-06-17T22:32:56Z

[Gas - 01] - Gauges could mint directly to Minter

Great idea, however in lack of POC and before / after I can't do anything but give it 0 gas saved

[Gas - 02] - In Minter, token could be made immutable

Agree with caching token in Minter, will save at least one cold SLOAD = 2100 Disagree with second comment

[Gas - 03] - In Minter, currentInflationAmountLp, currentInflationAmountKeeper, currentInflationAmountAmm could be made internal.

Will save deployment cost while removing certain functions / interface

[Gas - 04] - Minter and BkdToken could be merged

I think the ideas are really good, but you're gonna have to code a POC for these to be considered

[Gas - 05] In VestedEscrow, totalTime could be made immutable.

Similar to 3. Saves 2.1k

[Gas - 06] In Minter, there is no need to inherit ReentrancyGuard

I'm going to disagree in lack of detailed POC, especially given the un-CEIness of https://github.com/code-423n4/2022-05-backd/blob/2a5664d35cde5b036074edef3c1369b984d10010/protocol/contracts/tokenomics/Minter.sol#L217

#1 - GalloDaSballo

2022-06-17T22:34:05Z

Total Gas Saved: 4200

To be honest had the warden written some POC for the other refactorings this may have been the shortest yet most effective report of the contest

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