Shell Protocol - markus_ether's results

A set of EVM-based smart contracts on Arbitrum One. In a nutshell it is DeFi made simple.

General Information

Platform: Code4rena

Start Date: 21/08/2023

Pot Size: $36,500 USDC

Total HM: 1

Participants: 43

Period: 7 days

Judge: Dravee

Id: 277

League: ETH

Shell Protocol

Findings Distribution

Researcher Performance

Rank: 11/43

Findings: 1

Award: $1,933.59

🌟 Selected for report: 0

🚀 Solo Findings: 0

Findings Information

🌟 Selected for report: Mirror

Also found by: ItsNio, T1MOH, Testerbot, Udsen, d3e4, ktg, markus_ether, mert_eren, oakcobalt, pontifex, prapandey031, skodi

Labels

bug
3 (High Risk)
satisfactory
sufficient quality report
edited-by-warden
duplicate-57

Awards

1933.5938 USDC - $1,933.59

External Links

Lines of code

https://github.com/code-423n4/2023-08-shell/blob/c61cf0e01bada04c3d6055acb81f61955ed600aa/src/proteus/EvolvingProteus.sol#L563-L595

Vulnerability details

Impact

When depositing or withdrawing liquidity and specifying the amount of reserve tokens, the protocol bypasses crucial checks ensuring the amounts locked don't become too small. This oversight allows a malicious actor to remove all but a negligible quantity of liquidity from the pool, rendering the trading pool ineffectual. With low and imbalanced liquidity, rounding errors escalate, causing losses for liquidity providers.

Proof of Concept

When depositing or withdrawing liquidity by specifying the LP tokens, the flow enforces the function _checkBalances at the end of the execution.

The code enforces the following conditions:

if (x < MIN_BALANCE || y < MIN_BALANCE) revert BalanceError(x,y); int128 finalBalanceRatio = y.divi(x); if (finalBalanceRatio < MIN_M) revert BoundaryError(x,y); else if (MAX_M <= finalBalanceRatio) revert BoundaryError(x,y);

In simpler terms: The balances of x and y must exceed a specific minimum threshold, and the ratio of y/x should fall within a defined range.

This check is overlooked when specifying the amount of reserve tokens. Amounts of reserve tokens are specified when calling depositGivenInputAmount or withdrawGivenOutputAmount. Thus, it's possible to shift the pool into an insecure state that breaches the designated safety zones. The proof of concept below demonstrates a scenario where a user withdraws all but 1 Wei of token X from the pool:

function testWithdrawAboveLimit(uint256 x0, uint256 y0, uint256 s0) public {
    vm.assume(x0 >= uint256(MIN_BALANCE));
    vm.assume(y0 >= uint256(MIN_BALANCE));
    vm.assume(s0 >= uint256(MIN_BALANCE));
    vm.assume(y0/x0 <= MAX_BALANCE_AMOUNT_RATIO);
    vm.assume(x0/y0 <= MAX_BALANCE_AMOUNT_RATIO);

    // deploy with default
    proteus = new EvolvingInstrumentedProteus(
    ABDKMath64x64.divu(695000000000000, 1e18),
    ABDKMath64x64.divu(692930500000000, 1e18),
    ABDKMath64x64.divu(695000000000000, 1e18),
    ABDKMath64x64.divu(692930500000000, 1e18),
    3 days
);

    // attempt to withdraw all (but 1 wei) of the provided x liquidity
    SpecifiedToken token = SpecifiedToken.X;
    uint256 witdrawnAmount = x0 - 1;

    try proteus.withdrawGivenOutputAmount(x0, y0, s0, witdrawnAmount, token) returns (uint256 tokenBurned) {
        if (tokenBurned < s0 && witdrawnAmount >= MIN_OPERATING_AMOUNT) {
            uint256 x1 = x0 - witdrawnAmount;
            uint256 y1 = y0;
            assertGe(x1, uint256(MIN_BALANCE), "min balance");
            assertLe(y1 / x1, MAX_BALANCE_AMOUNT_RATIO, "wrong ratio, x too small");
            assertLe(x1 / y1, MAX_BALANCE_AMOUNT_RATIO, "wrong ratio, x too large");
        }
    } catch {}
}

Once liquidity is removed, further LPs are prevented from depositing or withdrawing when specifying the LP tokens via withdrawGivenInputAmount or depositGivenOutputAmount since these functions impose the check. Additionally, the rounding errors in the protocol surpass the anticipated values for current Liquidity Providers and Traders, leading to direct losses for Liquidity Providers.

Tools Used

Manual review

Incorporate the condition at the end of the function:

function _lpTokenSpecified(
...
) internal view returns (int256 computedAmount) {
+    // balance checks with consideration the computed amount
+    if (specifiedToken == SpecifiedToken.X) {
+        computedAmount = _applyFeeByRounding(xf - xi, feeDirection);
+        _checkBalances(xi + computedAmount, yf);
+    } else {
+        computedAmount = _applyFeeByRounding(yf - yi, feeDirection);
+        _checkBalances(xf, yi + computedAmount);
+    }
}

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-08-29T01:16:24Z

0xRobocop marked the issue as sufficient quality report

#1 - c4-pre-sort

2023-08-29T01:16:54Z

0xRobocop marked the issue as duplicate of #268

#2 - c4-judge

2023-09-11T19:26:05Z

JustDravee marked the issue as satisfactory

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