# FPMM

The **FPMM** contract is the core pool of Mento V3. Each instance holds two ERC-20 tokens (reserves), executes swaps at the **oracle rate** (minus fees), and allows **rebalancing** only by allowlisted liquidity strategies when the reserve-implied price deviates from the oracle by at least a configured threshold.

**Contract:** [mento-protocol/mento-core](https://github.com/mento-protocol/mento-core) — `contracts/swap/FPMM.sol`

***

## Interacting with the pool (code examples)

The snippets below show how to use the FPMM from Solidity. You need the **pool address** (from the factory or deployments) and the **token addresses** (pool uses sorted order: `token0` < `token1`). For most app flows, the [Router](https://docs.mento.org/mento-v3/build/smart-contracts/router) or the [Mento SDK](https://docs.mento.org/mento-v3/build/mento-sdk) simplify quoting and execution.

### Finding the pool

Get the pool for a token pair from the factory (tokens are sorted by address):

```solidity
IFPMMFactory factory = IFPMMFactory(factoryAddress);
(address token0, address token1) = factory.sortTokens(usdc, usdm);
address pool = factory.getPool(token0, token1);
// pool == address(0) if not deployed yet
```

### Quoting a swap

Get the expected amount of output token for a given amount in. The quote uses the **oracle rate** minus fees. Reverts if the oracle is invalid (e.g. market closed, breaker tripped, stale).

```solidity
IFPMM pool = IFPMM(poolAddress);
uint256 amountIn = 1_000_000; // 1 USDC (6 decimals)
uint256 amountOut = pool.getAmountOut(amountIn, usdc);
// amountOut is in output token decimals (e.g. USDm)
```

### Executing a swap (via Router)

The Router handles token transfers and calls the pool’s `swap` with the correct output amount. **Approve the router** to spend `amountIn` of the input token, then:

```solidity
IRouter router = IRouter(routerAddress);
IRouter.Route[] memory routes = new IRouter.Route[](1);
routes[0] = IRouter.Route({ from: usdc, to: usdm, factory: address(0) });

uint256[] memory amounts = router.swapExactTokensForTokens(
    amountIn,
    amountOutMin,   // slippage: minimum amount out (e.g. amountOut * 99 / 100)
    routes,
    msg.sender,
    block.timestamp + 300
);
// amounts[0] == amountIn, amounts[1] == actual amount out
```

### Executing a swap (direct pool call)

The pool’s `swap(amount0Out, amount1Out, to, data)` is **output-specified**. The caller must ensure the pool receives the **input** tokens (e.g. by implementing `IFPMMCallee` and passing `data` so the pool calls back into your contract, which then transfers the input tokens). For simple EOA flows, use the Router instead.

### Checking rebalance state

Useful for keepers or UIs to see if the pool is eligible for rebalance and the current deviation:

```solidity
IFPMM pool = IFPMM(poolAddress);
(
    uint256 oraclePriceNumerator,
    uint256 oraclePriceDenominator,
    uint256 reservePriceNumerator,
    uint256 reservePriceDenominator,
    bool reservePriceAboveOraclePrice,
    uint16 rebalanceThreshold,
    uint256 priceDifference
) = pool.getRebalancingState();
// Rebalance is allowed only when priceDifference >= rebalanceThreshold
```

### Adding and removing liquidity

Mint: transfer **both** tokens to the pool in the **current reserve ratio**, then call `mint(to)`. Burn: transfer LP tokens to the pool, then call `burn(to)`.

```solidity
// Mint: ensure pool has received token0 and token1 in correct ratio
IERC20(pool.token0()).transfer(pool, amount0);
IERC20(pool.token1()).transfer(pool, amount1);
uint256 liquidity = pool.mint(msg.sender);

// Burn: transfer LP tokens to pool first
IERC20(pool).transfer(pool, liquidity);
(uint256 amount0, uint256 amount1) = pool.burn(msg.sender);
```

***

## Invariants (design)

The contract enforces:

1. **Swap** does not decrease the total value of the pool (value at oracle, after crediting fees).
2. **Rebalance** reduces the price difference and keeps the same direction (above oracle stays above, below stays below).
3. **Rebalance** keeps the post-rebalance price difference at or above the configured threshold (no overshoot past the band).
4. **Rebalance** does not decrease reserve value more than the rebalance incentive (minimum repayment / incentive cap).

***

## State (FPMMStorage)

| Field                                                | Meaning                                                                                                                           |
| ---------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------- |
| `token0`, `token1`                                   | Pool tokens, ordered by address (token0 < token1).                                                                                |
| `decimals0`, `decimals1`                             | Scaling: `10^decimals` for each token (capped at 18).                                                                             |
| `reserve0`, `reserve1`                               | Last known reserve balances (updated after swap/mint/burn/rebalance).                                                             |
| `oracleAdapter`                                      | Contract used for the oracle rate; pool calls `getFXRateIfValid(referenceRateFeedID)`.                                            |
| `referenceRateFeedID`                                | Rate feed ID passed to the oracle adapter (e.g. address derived from pair identifier).                                            |
| `invertRateFeed`                                     | If true, the adapter rate is inverted (denominator/numerator) for quoting.                                                        |
| `lpFee`, `protocolFee`                               | Swap fees in basis points (BPS, denominator 10,000). Combined max 200 (2%).                                                       |
| `protocolFeeRecipient`                               | Receives protocol fee.                                                                                                            |
| `feeSetter`                                          | Address (with owner) allowed to set fees.                                                                                         |
| `rebalanceIncentive`                                 | Max incentive in BPS (max 100 = 1%) for rebalance; enforces minimum token-in.                                                     |
| `rebalanceThresholdAbove`, `rebalanceThresholdBelow` | Min price difference (BPS) to allow rebalance when reserve price > oracle or < oracle. Above max 10,000 bps; below max 5,000 bps. |
| `liquidityStrategy[addr]`                            | If true, `addr` may call `rebalance`.                                                                                             |
| `tradingLimits[token]`                               | Per-token TradingLimitsV2 config and state (5-min and 1-day net-flow caps).                                                       |

***

## Initialization

`initialize(token0, token1, oracleAdapter, referenceRateFeedID, invertRateFeed, initialOwner, params)` sets tokens, oracle adapter, rate feed ID, invert flag, owner, and `FPMMParams` (fees, fee recipient, fee setter, rebalance incentive, rebalance thresholds). Tokens are **not** re-sorted in `initialize`; the factory passes them already sorted. Token decimals must be ≤ 18.

***

## Oracle rate

The pool uses **oracle rate** for all swap quotes and value checks. It does **not** use a reserve-based curve.

* **Source:** `_getRateFeed()` calls `oracleAdapter.getFXRateIfValid(referenceRateFeedID)`. If the adapter reverts (e.g. `FXMarketClosed`, `TradingSuspended`, `NoRecentRate`), the pool reverts.
* **Invert:** If `invertRateFeed` is true, the rate is swapped to (denominator, numerator) so that the pool’s quote direction matches the intended token pair (e.g. token1/token0).

***

## Swap

* **Quote:** `getAmountOut(amountIn, tokenIn)` returns the amount of the other token at the **oracle rate** minus combined fee:\
  `rate × amountIn × (BPS - lpFee - protocolFee) / BPS`, with decimal scaling.\
  Reverts if the adapter’s `getFXRateIfValid` fails.
* **Execution:** `swap(amount0Out, amount1Out, to, data)` is **output-specified**: exactly one of `amount0Out` or `amount1Out` must be > 0. The pool transfers the output to `to`, then, if `data.length > 0`, calls `IFPMMCallee(to).hook(...)` so the caller can supply the input tokens. The pool infers `amount0In` and `amount1In` from balance deltas.
* **Checks:**
  1. **Value protection:** `_swapCheck`: reserve value at oracle (in token1, 1e18-scaled) after the swap must be ≥ initial value + fee value; otherwise `ReserveValueDecreased`.
  2. **TradingLimitsV2:** After the swap, `_applyTradingLimits` is called for each token (fee is deducted from inflow before net flow). If a limit is exceeded, the swap reverts.
* **Fees:** Protocol fee is transferred to `protocolFeeRecipient`; LP fee remains in the pool (increases reserve value). Reserves are synced with `_update()`.

***

## Mint and burn

* **Mint:** `mint(to)` — Caller must have transferred token0 and token1 to the pool in the **current reserve ratio**. Liquidity is computed: first mint `sqrt(amount0*amount1) - MINIMUM_LIQUIDITY` (with `MINIMUM_LIQUIDITY` locked at `address(1)`); subsequent mints proportional to reserves. Shares are minted to `to`.
* **Burn:** `burn(to)` — Caller must have transferred LP tokens to the pool. Burn is pro-rata: amounts out = (liquidity / totalSupply) × reserves; tokens sent to `to`, reserves updated.

***

## Rebalance

* **Entrypoint:** `rebalance(amount0Out, amount1Out, data)`. Only callable by an address with `liquidityStrategy[msg.sender] == true`.
* **Preconditions:**
  * Exactly one of `amount0Out`, `amount1Out` > 0.
  * Reserve price deviation from oracle ≥ threshold (above or below depending on direction).
  * Pool has sufficient liquidity for the requested output.
* **Flow:**
  1. Pool transfers the requested token(s) to `msg.sender` (the strategy).
  2. Pool calls `ILiquidityStrategy(msg.sender).onRebalance(msg.sender, amount0Out, amount1Out, data)`.
  3. Strategy must send back the **other** token (and optionally keep an incentive within the cap).
  4. Pool infers `amount0In`, `amount1In` from balance deltas.
  5. `_rebalanceCheck(swapData)` enforces: price difference **improves**, **direction preserved**, no overshoot (new difference ≥ threshold), and minimum repayment (incentive cap).
* **Rebalancing state:** `getRebalancingState()` returns oracle price, reserve price, whether reserve price is above oracle, the applicable threshold, and current price difference in BPS. Off-chain or strategy logic can use this to decide rebalance size.

***

## TradingLimitsV2

* **Config:** `configureTradingLimit(token, config)` (owner) sets the TradingLimitsV2 config for that token (e.g. 5-min and 1-day limits).
* **Application:** After every swap, `_applyTradingLimits(token0, ...)` and `_applyTradingLimits(token1, ...)` update state and revert if a limit is exceeded. Fee is deducted from inflow before net flow.
* **View:** `getTradingLimits(token)` returns config and state for the token.

***

## Admin

| Function                                                          | Restriction     | Effect                                 |
| ----------------------------------------------------------------- | --------------- | -------------------------------------- |
| `setLPFee`, `setProtocolFee`                                      | `onlyFeeSetter` | Combined with the other fee ≤ 200 bps. |
| `setProtocolFeeRecipient`                                         | owner           |                                        |
| `setFeeSetter`                                                    | owner           |                                        |
| `setRebalanceIncentive`                                           | `onlyFeeSetter` | Max 100 bps.                           |
| `setRebalanceThresholds(above, below)`                            | owner           | Above ≤ 10,000 bps; below ≤ 5,000 bps. |
| `setOracleAdapter`, `setReferenceRateFeedID`, `setInvertRateFeed` | owner           |                                        |
| `setLiquidityStrategy(addr, allowed)`                             | owner           | Allowlist for `rebalance`.             |
| `configureTradingLimit(token, config)`                            | owner           | TradingLimitsV2 per token.             |

***

## Key errors

* `ReserveValueDecreased` — Swap would decrease pool value at oracle after fees.
* `NotLiquidityStrategy` — Caller of `rebalance` not allowlisted.
* `PriceDifferenceTooSmall` — Rebalance called but deviation below threshold.
* `PriceDifferenceNotImproved`, `PriceDifferenceMovedInWrongDirection`, `PriceDifferenceMovedTooFarFromThresholds` — Rebalance acceptance failed.
* `InsufficientAmount0In` / `InsufficientAmount1In` — Strategy did not return enough of the other token (incentive cap).
* `TradingSuspended`, `NoRecentRate`, `FXMarketClosed` — From oracle adapter when rate is invalid.

***

## See also

* [FPMMFactory](https://docs.mento.org/mento-v3/build/smart-contracts/fpmmfactory) — Deployment and default params.
* [OracleAdapter](https://docs.mento.org/mento-v3/build/smart-contracts/oracleadapter) — How the pool gets the rate.
* [TradingLimitsV2](https://docs.mento.org/mento-v3/build/smart-contracts/tradinglimits) — Net-flow caps.
* [Liquidity strategies](https://docs.mento.org/mento-v3/build/smart-contracts/liquidity-strategies) — Who can call rebalance and how.
* [Dive Deeper: FPMMs](https://docs.mento.org/mento-v3/dive-deeper/fpmm) — Design and invariant.
