Sushi Trident contest phase 1 - 0xsanson's results

Community-driven DeFi platform

General Information

Platform: Code4rena

Start Date: 16/09/2021

Pot Size: $200,000 SUSHI

Total HM: 26

Participants: 16

Period: 14 days

Judge: alcueca

Total Solo HM: 13

Id: 29

League: ETH

Sushi

Findings Distribution

Researcher Performance

Rank: 4/16

Findings: 8

Award: $13,774.94

🌟 Selected for report: 6

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: broccoli

Also found by: 0xsanson, cmichel

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

190.6158 SUSHI - $2,382.70

External Links

Handle

0xsanson

Vulnerability details

Impact

The flashswap function in IndexPool.sol doesn't fulfill its function. Indeed it should transfer tokens to the users before they need to pay back, but the transfer happens at the end:

...
ITridentCallee(msg.sender).tridentSwapCallback(context);
// @dev Check Trident router has sent `amountIn` for skim into pool.
unchecked { // @dev This is safe from under/overflow - only logged amounts handled.
    require(_balance(tokenIn) >= amountIn + inRecord.reserve, "NOT_RECEIVED");
    inRecord.reserve += uint120(amountIn);
    outRecord.reserve -= uint120(amountOut);
}
_transfer(tokenOut, amountOut, recipient, unwrapBento);
...

Move _transfer before the callback.

#0 - alcueca

2021-10-30T04:39:33Z

Duplicate of #26

Findings Information

🌟 Selected for report: broccoli

Also found by: 0xsanson, WatchPug, cmichel

Labels

bug
duplicate
3 (High Risk)
sponsor confirmed

Awards

128.6657 SUSHI - $1,608.32

External Links

Handle

0xsanson

Vulnerability details

Impact

In IndexPool.sol, the function _pow is called during the computation of the output amount when swapping.

function _pow(uint256 a, uint256 n) internal pure returns (uint256 output) {
    output = n % 2 != 0 ? a : BASE;
    for (n /= 2; n != 0; n /= 2)
        a = a * a;
        if (n % 2 != 0) output = output * a;
}

It's my understanding that it should calculate the exponentiation _pow(a,n) = a**n. However a simple test shows it doesn't work: _pow(3,5) = 3, _pow(3,4) = 10**18.

Consider documenting the function (and write some tests to check it works). If it needs to compute the exponentiation, one correct way would be (check wiki for alternatives):

function _pow(uint256 a, uint256 n) internal pure returns (uint256 output) {
    if (n == 0) {
        output = 1;
        return output;
    }
    uint256 y = 1;
    while (n > 1) {
        if (n % 2 == 0) {
            a = a*a;
            n = n/2;
        } else {
            y = a*y;
            a = a*a;
            n = (n-1)/2;
        }
    }
    output = a*y;
}

#0 - alcueca

2021-10-27T04:14:16Z

Duplicate with #27

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xsanson, WatchPug

Labels

bug
duplicate
3 (High Risk)

Awards

190.6158 SUSHI - $2,382.70

External Links

Handle

0xsanson

Vulnerability details

Impact

In HybridPool's _updateReserves() function, the reserves are calculated as:

(uint256 _reserve0, uint256 _reserve1) = _balance();

This is incorrect, since reserves are a bento-share quantity, while _balance() outputs a bento-amount quantity. This basically inflates artificially the reserves causing accounting issues.

The correct version should be uint256 _reserve0 = __balance(token0); (the double-underscore balance returns a share quantity).

Findings Information

🌟 Selected for report: 0xsanson

Also found by: pauliax

Labels

bug
2 (Med Risk)
sponsor confirmed

Awards

95.3079 SUSHI - $1,191.35

External Links

Handle

0xsanson

Vulnerability details

Impact

IndexPool doesn't collect fees for barFeeTo. Since this Pool contains also a method updateBarFee(), probably this is an unintended behavior. Also without a fee, liquidity providers would probably ditch ConstantProductPool in favor of IndexPool (using the same two tokens with equal weights), since they get all the rewards. This would constitute an issue for the ecosystem.

Add a way to send barFees to barFeeTo, same as the other pools.

Findings Information

🌟 Selected for report: cmichel

Also found by: 0xsanson

Labels

bug
duplicate
2 (Med Risk)
disagree with severity

Awards

95.3079 SUSHI - $1,191.35

External Links

Handle

0xsanson

Vulnerability details

Impact

In HybridPool's flashSwap function there's a transfer to barFeeTo

_transfer(tokenIn, fee, barFeeTo, false);

Here fee = (amountIn * swapFee) / MAX_FEE is the total swap fee. However it should transfer out only a fraction of it (barFee/MAX_FEE) otherwise liquidity providers wouldn't get any reward.

Calculate the appropriate fee to send to the barFeeTo.

#0 - maxsam4

2021-10-06T08:03:02Z

Yep, we've revamped fee handling since.

#1 - maxsam4

2021-10-06T08:07:35Z

No user funds at risk, just potential fee might go to sushi rather than LPs (which can be sent back). So, I'd suggest med risk.

Findings Information

🌟 Selected for report: 0xsanson

Also found by: broccoli

Labels

bug
duplicate
2 (Med Risk)

Awards

95.3079 SUSHI - $1,191.35

External Links

Handle

0xsanson

Vulnerability details

Impact

In HybridPool.sol, functions _computeLiquidityFromAdjustedBalances, _getY and _getYD may finish before approximation converge, since it's limited by MAX_LOOP_LIMIT iterations. In this situation the final estimated value will still be treated as correct, even though it could be relatively inaccurate.

Consider reverting the transactions if this doesn't occur. See https://blog.openzeppelin.com/saddle-contracts-audit/ issue [M03], with their relative fix.

Findings Information

🌟 Selected for report: nikitastupin

Also found by: 0xRajeev, 0xsanson, broccoli, t11s

Labels

bug
duplicate
1 (Low Risk)

Awards

9.2639 SUSHI - $115.80

External Links

Handle

0xsanson

Vulnerability details

Impact

The TridentERC20 token uses EIP-712 for the permit function. This is correctly implemented but DOMAIN_SEPARATOR is assumed constant, false when block.chainid changes (for example after an hard fork). This assumption can cause replay attacks on an eventual fork of the chain.

Consider implementing a variable DOMAIN_SEPARATOR. For a possible implementation see @openzeppelin/contracts/utils/cryptography/draft-EIP712.sol

#0 - sarangparikh22

2021-10-05T08:29:12Z

Duplicate of #11

Findings Information

🌟 Selected for report: 0xsanson

Labels

bug
1 (Low Risk)
sponsor disputed

Awards

70.5984 SUSHI - $882.48

External Links

Handle

0xsanson

Vulnerability details

Impact

In IndexPool.sol, the internal pure function _powApprox is called during the computation of the output amount when swapping. It contains a for-loop that exits when a term reaches a certain precision.

for (uint256 i = 1; term >= precision; i++) {
	...
}

The issue is that there's no evident way that the iteration converges. At least it's possible that the number of necessary cycles is high enough to consume all transaction's gas. Also its function isn't really clear. If it wants to calculate an approximated exp, it actually doesn't work for small numbers (tested on ~10**18), so also a check on the input parameters may be needed.

This function needs some specific tests to show that it works without problems for all possible realistic inputs. Specifically check if a MAX_ITERATIONS parameter is needed. Also consider adding in the function's natSpec a link to the mathematical proof on how the formula works.

#0 - maxsam4

2021-10-06T08:41:41Z

The function has been borrowed from https://github.com/balancer-labs/balancer-core/blob/f4ed5d65362a8d6cec21662fb6eae233b0babc1f/contracts/BNum.sol#L128 where it is well tested.

We'll add more tests for it but there's no apparent vulnerability here.

#1 - alcueca

2021-10-25T05:18:26Z

Downgraded to severity 1 on grounds of lack of documentation with implied risks.

Findings Information

🌟 Selected for report: 0xsanson

Labels

bug
1 (Low Risk)
sponsor acknowledged

Awards

70.5984 SUSHI - $882.48

External Links

Handle

0xsanson

Vulnerability details

Impact

Using BentoBox conventions, shares and amounts are different (they are a base and an elastic quantity respectively). In HybridPool, balances are defined as amounts. This is respected correctly in all code but in one calculation in the burn function (L129) and one in burnSingle (L161,L170):

balance0 -= _toShare(token0, amount0);
// idem 1

The _toShare conversion should be wrong, and can cause serious accounting issues.

Replace with balance0 -= amount0;

#0 - maxsam4

2021-10-06T08:28:23Z

Those variables are not used anywhere after they are updated. therefore, this is a no-op. Non-critical issue IMO.

#1 - alcueca

2021-10-25T06:13:06Z

-No assets at risk, but function incorrect as to spec. Sev 1.

Findings Information

🌟 Selected for report: 0xsanson

Labels

bug
1 (Low Risk)
sponsor acknowledged

Awards

70.5984 SUSHI - $882.48

External Links

Handle

0xsanson

Vulnerability details

Impact

In HybridPool's _computeLiquidityFromAdjustedBalances there's the following operation:

D = (((N_A * s) / A_PRECISION + 2 * dP) * D) / ((N_A / A_PRECISION - 1) * D + 3 * dP);

The division by A_PRECISION in N_A / A_PRECISION can be moved after a multiplication to lose less precision. See the implementation by Curve.

Consider changing the order of the operations like in the link provided.

Findings Information

🌟 Selected for report: 0xsanson

Labels

bug
duplicate
1 (Low Risk)
sponsor acknowledged

Awards

70.5984 SUSHI - $882.48

External Links

Handle

0xsanson

Vulnerability details

Impact

The _getY and _getYD functions in HybridPool contain the following:

c = (c * D) / ((N_A * 2) / A_PRECISION);

Consider rewriting it like this, since performing the multiplications before the division lowers the risk of losing precision.

c = c * D * A_PRECISION / (N_A * 2);

There shouldn't be any overflow since Curve does it this way and it hasn't encountered any problem yet.

Findings Information

🌟 Selected for report: gpersoon

Also found by: 0xsanson

Labels

bug
duplicate
G (Gas Optimization)

Awards

14.5161 SUSHI - $181.45

External Links

Handle

0xsanson

Vulnerability details

Impact

In ConstantProductPool's _update, if (blockTimestampLast == 0) can be rewritten to if (_blockTimestampLast == 0) since the variable in memory is equal to the storage so far. This saves an SLOAD.

#0 - sarangparikh22

2021-10-05T08:51:17Z

Duplicate of #16

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