Shell Protocol - Mirror'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: 1/43

Findings: 2

Award: $2,522.83

QA:
grade-b

🌟 Selected for report: 1

🚀 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)
primary issue
selected for report
upgraded by judge
sufficient quality report
H-01

Awards

2513.6719 USDC - $2,513.67

External Links

Lines of code

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

Vulnerability details

Description

The pool's ratio of y to x must be within the interval [MIN_M, MAX_M), which will be checked by the _checkBalances() function. External view functions will call _swap(), _reserveTokenSpecified() or _lpTokenSpecified() functions to get the specified result. However, _checkBalances() is only used in the _swap() and _lpTokenSpecified() functions. There is no balance validation for depositGivenInputAmount() and withdrawGivenOutputAmount() functions, which use _reserveTokenSpecified() function.

Impact

If there's no other validation outside these two functions, user deposits/withdraws may break the invariant, i.e. the pool's ratio of y to x is outside the interval [MIN_M, MAX_M).

Proof of Concept

Add the following code in test/EvolvingProteusProperties.t.sol file EvolvingProteusProperties contract, and run forge test --mt RatioOutsideExpectedInterval.

function testDepositRatioOutsideExpectedInterval(uint256 x0, uint256 y0, uint256 s0, uint256 depositedAmount) public {
  int128 MIN_M = 0x00000000000002af31dc461;
  uint256 INT_MAX_SQRT = 0xb504f333f9de6484597d89b3754abe9f;

  vm.assume(x0 >= MIN_BALANCE && x0 <= INT_MAX_SQRT);
  vm.assume(y0 >= MIN_BALANCE && y0 <= INT_MAX_SQRT);
  vm.assume(s0 >= MIN_BALANCE && s0 <= INT_MAX_SQRT);
  vm.assume(depositedAmount >= MIN_OPERATING_AMOUNT && depositedAmount < INT_MAX_SQRT && depositedAmount >= 2 * uint256(FIXED_FEE));
  vm.assume(y0/x0 <= MAX_BALANCE_AMOUNT_RATIO);
  vm.assume(x0/y0 <= MAX_BALANCE_AMOUNT_RATIO);
  vm.assume(int256(y0).divi(int256(x0) + int256(depositedAmount)) < MIN_M);   // breaks the invariant
  SpecifiedToken depositedToken = SpecifiedToken.X;
  
  vm.expectRevert();  // There should be at least one case that call did not revert as expected
  DUT.depositGivenInputAmount(
      x0,
      y0,
      s0,
      depositedAmount,
      depositedToken
  );
}

function testWithdrawRatioOutsideExpectedInterval(uint256 x0, uint256 y0, uint256 s0, uint256 withdrawnAmount) public {
  int128 MIN_M = 0x00000000000002af31dc461;
  uint256 INT_MAX_SQRT = 0xb504f333f9de6484597d89b3754abe9f;

  vm.assume(x0 >= MIN_BALANCE && x0 <= INT_MAX_SQRT);
  vm.assume(y0 >= MIN_BALANCE && y0 <= INT_MAX_SQRT);
  vm.assume(s0 >= MIN_BALANCE && s0 <= INT_MAX_SQRT);
  vm.assume(withdrawnAmount >= MIN_OPERATING_AMOUNT && withdrawnAmount < INT_MAX_SQRT && withdrawnAmount >= 2 * uint256(FIXED_FEE));
  vm.assume(y0/x0 <= MAX_BALANCE_AMOUNT_RATIO);
  vm.assume(x0/y0 <= MAX_BALANCE_AMOUNT_RATIO);
  vm.assume(withdrawnAmount < y0);    // no more than balance
  vm.assume((int256(y0) - int256(withdrawnAmount)).divi(int256(x0)) < MIN_M);   // breaks the invariant
  SpecifiedToken withdrawnToken = SpecifiedToken.Y;
  
  vm.expectRevert();  // There should be at least one case that call did not revert as expected
  DUT.withdrawGivenOutputAmount(
      x0,
      y0,
      s0,
      withdrawnAmount,
      withdrawnToken
  );
}

Tools Used

Manual

It's recommended to add _checkBalances(xi + specifiedAmount, yi) after L579 and add _checkBalances(xi, yi + specifiedAmount) after L582.

Assessed type

Invalid Validation

#0 - c4-pre-sort

2023-08-29T06:59:10Z

0xRobocop marked the issue as duplicate of #268

#1 - c4-pre-sort

2023-08-29T06:59:18Z

0xRobocop marked the issue as sufficient quality report

#2 - c4-judge

2023-09-11T19:20:48Z

JustDravee changed the severity to 3 (High Risk)

#3 - c4-judge

2023-09-11T19:21:01Z

JustDravee marked the issue as selected for report

#4 - JustDravee

2023-09-15T22:37:06Z

Awards

9.1555 USDC - $9.16

Labels

bug
grade-b
QA (Quality Assurance)
sufficient quality report
edited-by-warden
Q-19

External Links

Timestamp reliability on L2

Timestamp information on Arbitrum can be less reliable than on mainnet. As mentioned in Uniswap docs:

The block.timestamp of these blocks, however, reflect the block.timestamp of the last L1 block ingested by the Sequencer.

Redundant code

Typos in comments

#0 - c4-pre-sort

2023-08-30T04:08:15Z

0xRobocop marked the issue as sufficient quality report

#1 - c4-judge

2023-09-11T20:01:20Z

JustDravee marked the issue as grade-b

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