Backd Tokenomics contest - Ruhum'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: 11/58

Findings: 2

Award: $2,891.54

🌟 Selected for report: 1

🚀 Solo Findings: 1

Findings Information

🌟 Selected for report: Ruhum

Labels

bug
2 (Med Risk)
sponsor disputed

Awards

2732.5332 USDC - $2,732.53

External Links

Lines of code

https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/tokenomics/FeeBurner.sol#L43-L88 https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/swappers/SwapperRouter.sol#L414-L425 https://github.com/code-423n4/2022-05-backd/blob/main/protocol/contracts/swappers/SwapperRouter.sol#L439

Vulnerability details

Impact

While the SwapperRouter contract isn't explicitly in scope, it's a dependency of the FeeBurner contract which is in scope. So I think it's valid to make this submission.

The SwapperRouter contract uses the chainlink oracle to compute the minimum amount of tokens it should expect from the swap. The value is then used for the slippage check. But, if the chainlink oracle fails, for whatever reason, the contract uses 0 for the slippage check instead. Thus there's a scenario where swaps initiated by the FeeBurner contract can be sandwiched.

Proof of Concept

  1. multiple swaps initiated through FeeBurner.burnToTarget()
  2. SwapperRouter calls _minTokenAmountOut() to determine min_out parameter.
  3. minTokenAmountOut() returns 0 when Chainlink oracle fails

Tools Used

none

Either revert the transaction or initiate the transaction with a default slippage of 99%. In the case of Curve, you can get the expected amount through get_dy() and then multiply the value by 0.99. Use that as the min_out value and you don't have to worry about chainlink

#0 - chase-manning

2022-06-06T11:48:24Z

This is intended functionality. If there is no oracle for a token, we still want to swap it, even if this presents a possible sandwich attack. It should be rare for a token to not have an oracle, and when it does we would rather accept slippage as opposed to not being able to swap it at all.

#1 - GalloDaSballo

2022-06-19T16:57:06Z

I acknowledge the sponsor reply that they want to offer a service to the end user in allowing any swappable token to be used.

While I believe the intent of the sponsor is respectable, the reality of the code is that it indeed allows for price manipulation and extraction of value, personally I would recommend end users to perform their own swaps to ensure a more reliable outcome.

That said, because the code can be subject to leak of value, I believe Medium Severity to be appropriate

Awards

159.0051 USDC - $159.01

Labels

bug
QA (Quality Assurance)
resolved
sponsor confirmed

External Links

Report

Low

L-01: use two-step process for critical address changes

Consider using a two-step process for transferring the ownership of a contract. While it costs a little more gas, it's safer than transferring directly.

Here's an example from the Compound Timelock contract: https://github.com/compound-finance/compound-protocol/blob/master/contracts/Timelock.sol#L45-L58

Relevant code sections:

Non-Critical

N-01: emit an event when changing the configuration of a contract

There are multiple configuration functions that don't emit an event.

Relevant code:

There're probably a couple more that I missed

N-02: AmmGauge doesn't use correct value for staking/unstaking events

The functions verify the number of tokens that were transferred. The value is used to keep track of the internal balances. But, it isn't used for the event. There you use the user specified amount parameter:

function stakeFor(address account, uint256 amount) public virtual override returns (bool) {
    require(amount > 0, Error.INVALID_AMOUNT);

    _userCheckpoint(account);

    uint256 oldBal = IERC20(ammToken).balanceOf(address(this));
    IERC20(ammToken).safeTransferFrom(msg.sender, address(this), amount);
    uint256 newBal = IERC20(ammToken).balanceOf(address(this));
    uint256 staked = newBal - oldBal;
    balances[account] += staked;
    totalStaked += staked;
    
    // should be `staked` and not `amount`
    emit AmmStaked(account, ammToken, amount);
    return true;
}

Relevant code:

#0 - GalloDaSballo

2022-06-20T00:31:51Z

## L-01: use two-step process for critical address changes Personally disagree, but finding is valid

N-01: emit an event when changing the configuration of a contract

Agree

N-02: AmmGauge doesn't use correct value for staking/unstaking events

Finding is valid, nice find

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