<!-- canonical: https://0xsimao.com/findings/mento-sell-supply-contribution-stuck -->

# User to sell the last supply will make the exchange contribution forever stuck

Medium · Sherlock · Stablecoin · 24th October 2024

Finding M-1 of the Mento competition.

- Protocol: https://audits.sherlock.xyz/contests/598
- Report: /reports/mento
- Codebase: https://github.com/0xsimao/2024-10-mento-update/tree/098b17fb32d294145a7f000d96917d13db8756cc
- Source: https://github.com/sherlock-audit/2024-10-mento-update-judging/issues/17

---

### Summary

[BancorExchangeProvider::_getScaledAmountOut()](https://github.com/sherlock-audit/2024-10-mento-update/blob/main/mento-core/contracts/goodDollar/BancorExchangeProvider.sol#L345) decreases the amount out when the token in is the supply token by the exit contribution, that is, `scaledAmountOut = (scaledAmountOut * (MAX_WEIGHT - exchange.exitContribution)) / MAX_WEIGHT;`.

Whenever the last supply of the token is withdrawn, it will get all the reserve and store it in `scaledAmountOut`, and then apply the exit contribution, leaving these funds forever stuck, as there is 0 supply to redeem it.

### Root Cause

In `BancorExchangeProvider:345`, the exchange contribution is applied regardless of there being supply left to redeem it.

### Internal pre-conditions

1. All supply must be withdrawn from the exchange.

### External pre-conditions

None.

### Attack Path

1. Users call `Broker::swapIn()`, that calls `GoodDollarExchangeProvider::swapIn()`, which is the exchange contract that holds the token and reserve balances, selling supply tokens until the supply becomes 0.

### Impact

The last exit contribution will be forever stuck. This amount is unbounded and may be very significant. 

### PoC

Add the following test to `BancorExchangeProvider.t.sol`:
```solidity
function test_POC_swapIn_whenTokenInIsToken_shouldSwapIn() public {
  BancorExchangeProvider bancorExchangeProvider = initializeBancorExchangeProvider();
  uint256 amountIn =  300_000 * 1e18;

  bytes32 exchangeId = bancorExchangeProvider.createExchange(poolExchange1);

  vm.startPrank(brokerAddress);
  uint256 amountOut = bancorExchangeProvider.swapIn(exchangeId, address(token), address(reserveToken), amountIn);

  (, , uint256 tokenSupplyAfter, uint256 reserveBalanceAfter, , ) = bancorExchangeProvider.exchanges(exchangeId);

  assertEq(amountOut, 59400e18);
  assertEq(reserveBalanceAfter, 600e18);
  assertEq(tokenSupplyAfter, 0);

  vm.expectRevert("ERR_INVALID_SUPPLY");
  bancorExchangeProvider.swapIn(exchangeId, address(token), address(reserveToken), 1e18);
}
```

### Mitigation

The specific mitigation depends on the design.

---

Related findings:

- [Yield form LRTs are forever stuck in the protocol and cannot be withdrawn](https://0xsimao.com/findings/autonomint-yield-form-stuck-withdrawn): Autonomint
- [Stuck ETH in Curve exchanges due to sending msg.value to the exchange instead of amountIn](https://0xsimao.com/findings/singularity-stuck-eth-curve-exchanges): Singularity
- [Malicious user can call `borrowing::calculateCumulativeRate()` any number of times to inflate debt rate as `lastEventTime` is not updated](https://0xsimao.com/findings/autonomint-calculate-times-inflate-debt): Autonomint
- [`totalCdsDepositedAmountWithOptionFees` is incorrectly reduced in `CDSLib::withdrawUser()`, leading to stuck option fees](https://0xsimao.com/findings/autonomint-fees-reduced-withdraw-stuck): Autonomint
