<!-- canonical: https://0xsimao.com/findings/tenor-markets-reverts-overflow-guard-runs -->

# ClampLib::mulDivDownInverse reverts on `target == type(uint256).max` before the overflow guard runs

Low/Info · Blackthorn · Fixed-rate lending · 22nd May, 2026

Finding L-3 of the Tenor Morpho Migrations security review.

- Protocol: https://www.tenor.finance/
- Report: /reports/tenor-markets
- Source: https://github.com/tenor-labs/tenor-contracts/blob/main/audits/2026-06-Blackthorn-review.pdf

---

## Summary

`mulDivDownInverse` computes `n = target + 1` before its overflow guard, so `target == type(uint256).max` reverts with arithmetic overflow instead of returning `type(uint256).max` as the guard's intent implies.

## Vulnerability Detail

The [function](https://github.com/sherlock-audit/2026-05-tenor-markets-may-21st-2026/blob/main/tenor-morpho-v2-contracts-2/src/periphery/libraries/ClampLib.sol#L232-L238):

```solidity
function mulDivDownInverse(uint256 target, uint256 den, uint256 num) internal pure returns (uint256) {
    uint256 n = target + 1;
    if (n == 0 || den > type(uint256).max / n) {
        return type(uint256).max; // overflow -> this constraint is not the bottleneck
    }
    return (n * den - 1) / num;
}
```

With Solidity 0.8.x checked arithmetic, `target + 1` reverts when `target == type(uint256).max`. The guard immediately below is meant to handle the overflow case by returning `type(uint256).max`, but it never executes.

`getOfferRemaining` reaches this function with `remainingAssets` derived from `offer.maxAssets`, which is a `uint256` controlled by the maker. A maker setting `offer.maxAssets = type(uint256).max` (or any value such that `remainingAssets` equals `type(uint256).max`) DoSes any helper that calls `getOfferRemaining` for that offer, including the router's clamp logic.

## Impact

Callers reading offer state for a maker-controlled `offer.maxAssets == type(uint256).max` revert instead of receiving the documented "unbounded" sentinel, blocking offer reads and any clamp-based path that consumes them.

## Code Snippet

https://github.com/sherlock-audit/2026-05-tenor-markets-may-21st-2026/blob/main/tenor-morpho-v2-contracts-2/src/periphery/libraries/ClampLib.sol#L233

## Tool Used

Manual Review

## Recommendation

Short-circuit before the addition:

```solidity
function mulDivDownInverse(uint256 target, uint256 den, uint256 num) internal pure returns (uint256) {
    if (target == type(uint256).max) return type(uint256).max;
    uint256 n = target + 1;
    if (den > type(uint256).max / n) {
        return type(uint256).max;
    }
    return (n * den - 1) / num;
}
```

---

Related findings:

- [Reentrancy guard can be implemented with a uint256 to save gas](https://0xsimao.com/findings/orange-reentrancy-guard-implemented-gas): Orange Bridge
- [Index underflow is not protected against, although it has no impact as the pool with index type(uint256).max should not be registered](https://0xsimao.com/findings/nftperp-ii-index-underflow-impact-registered): Nftperp Matching Engine
- [mul() and div() in NFTPMath are misleading due to scaling by 1e18 underneath](https://0xsimao.com/findings/nftperp-i-nftp-scaling-1e18-underneath): Nftperp Exchange
- [`LenderCommitmentGroup_Smart` does not use `mulDiv` when converting between token and share amounts, possibly leading to DoS or loss of funds](https://0xsimao.com/findings/teller-finance-mul-div-converting-share): Teller Finance
