Skip to content

feature: V3Provider - #151

Open
razww wants to merge 33 commits into
masterfrom
feature/v3-lp
Open

feature: V3Provider#151
razww wants to merge 33 commits into
masterfrom
feature/v3-lp

Conversation

@razww

@razww razww commented Mar 23, 2026

Copy link
Copy Markdown
Collaborator

Summary

A Lista V3 concentrated-liquidity collateral provider for the slisBNB/BNB pair, used as Moolah collateral. The original monolith exceeded EIP-170, so it is split into three UUPS contracts with clean boundaries, plus a dedicated liquidator:

  • V3Provider / SlisBNBV3Provider — ERC-4626 vLP shares that are the Moolah collateral token. Owns share accounting + Moolah wiring; holds no NFT. The slisBNB subclass adds slisBNBx reward mirroring, per-user deposit tracking, and the BOT-gated rebalance entry.
  • V3DexAdapter / SlisBNBV3DexAdapter — the sole V3 NFT + idle-inventory custodian and all NPM/pool math. The slisBNB subclass adds the exchange-rate-implied fair price, ±1% auto-centered range, and the rate-centered rebalance + StakeManager inventory conversion.
  • SlisBNBV3ProviderOracle — the Moolah market.oracle. Prices the share off the adapter's manipulation-resistant fair composition + the resilient oracle, with a capped haircut.
  • V3Liquidator — liquidator for V3-provider-backed markets: pre-funded liquidation, flash liquidation with callback share-redemption + DEX swap, and standalone share/token ops.

Architecture (3-contract split)

Contract One job Holds
V3Provider (vault) ERC-4626 shares, Moolah supply/withdraw, share accounting, CEI shares
V3DexAdapter (strategy) NFT custody, all NPM/pool writes, raw-NAV/composition views the V3 NFT + idle inventory
SlisBNBV3ProviderOracle (oracle) price the share for Moolah (fair view + haircut) nothing

The vault never touches the NFT/pool; the adapter never imports Moolah or the share token (it takes (shares, totalShares) as opaque fractions); the oracle reads only public views. Raw-NAV vs haircut is cleanly separated (adapter returns raw, oracle applies the only haircut).

Key Design Decisions

  • slisBNB/BNB only. The adapter and oracle constructors reject any non-slisBNB/WBNB pair (NotSlisBnbWbnbPair); the oracle additionally asserts its tokens match the adapter's (AdapterPairMismatch), so a mis-wired deploy can't silently misprice.
  • Exchange-rate oracle (not TWAP). The fair price is StakeManager-implied (convertSnBnbToBnb), not pool spot/TWAP — peek/totalAssets/getUserBalanceInBnb are invariant to pool-price manipulation. Per-leg USD comes from the resilient oracle; peek reverts on a zero leg price or zero total value (finding D); supply == 0 returns 0
    (pre-market).
  • Value-based shares. shares = addedValue × supplyBefore / totalValueBefore, valuing freshly-added liquidity at the fair price (first deposit denominated in the accounting asset, WBNB). Fees are compounded before minting so existing holders capture them before dilution.
  • Rate-centered rebalance. Recenters to the exchange-rate ±1% range, converts free inventory to the optimal ratio via the StakeManager (deposit/instantWithdraw, measured by balance delta — not pool-manipulable), guarded by a configurable rate-drift threshold (centerRateThresholdBps) + deadline + minLiquidity. BOT-gated on the vault;
    onlyProvider on the adapter.
  • Deposit-refund reentrancy fix (C-1). The unused-input refund is forwarded to the depositor after _mint + supplyCollateral (CEI), so the native-BNB refund callback can't observe an inflated share price (stale totalSupply vs. already-added NAV) and reenter Moolah.borrow. Withdraw/redeem likewise burn before the adapter pushes
    underlying to the receiver.
  • Idle-inventory tracking via idleToken0/idleToken1 storage (not balanceOf) so donations can't inflate NAV.
  • Native BNB support — deposits accept msg.value (auto-wrapped to WBNB); withdrawals unwrap WBNB to native BNB.
  • AccessControlEnumerableUpgradeable across vault/adapter/oracle/liquidator (codebase standard; enumerable roles).
  • IV3PoolMinimal slot0 read — decodes only sqrtPriceX96/tick, width-agnostic to feeProtocol (Uniswap uint8 vs PancakeSwap uint32), so the adapter works against any V3 fork.
  • V3 math sourced from the audited lista-dao-contracts libs; interfaces from the lista-v3 submodule.

Files

File Description
src/provider/V3Provider.sol Generic vault base — ERC-4626 shares + Moolah wiring + CEI
src/provider/SlisBNBV3Provider.sol slisBNB vault — slisBNBx mirror, deposit tracking, BOT rebalance
src/provider/V3DexAdapter.sol Generic DEX custodian — NFT + all NPM/pool math + raw-NAV views
src/provider/SlisBNBV3DexAdapter.sol slisBNB adapter — rate fair price, ±1% centering, rebalance + inventory conversion
src/provider/SlisBNBV3ProviderOracle.sol Moolah market.oracle — share pricing off fair view + haircut
src/provider/interfaces/*.sol IV3Provider, IV3DexAdapter, ISlisBNBV3DexAdapter, IV3ProviderOracle, IV3PoolMinimal, IStakeManager
src/provider/libraries/V3PositionLib.sol NPM mint/increase/decrease/collect/burn primitives
src/provider/libraries/SlisBnbInventoryLib.sol StakeManager inventory conversion (delegatecall lib)
src/liquidator/V3Liquidator.sol Liquidator: pre-funded + flash liquidation, share redemption, DEX swaps
test/provider/SlisBNBV3Provider.t.sol Functional suite (slisBNB/WBNB fork, block 60541406)
test/provider/SlisBNBV3ProviderRate.t.sol Rate-path invariance + rebalance
test/provider/V3ProviderReentrancyPoC.t.sol C-1 deposit-refund reentrancy regression
test/liquidator/V3Liquidator.t.sol Liquidator fork tests

Test Plan

All suites fork BSC mainnet at block 60541406 (slisBNB/WBNB 1bp pool; the live StakeManager is etched with a faithful instantWithdraw stand-in for the rebalance conversion).

  • Functional (SlisBNBV3Provider.t.sol) — deposit/withdraw/redeem/supplyShares/withdrawShares, value-based share math, previews, oracle peek/getTokenConfig + finding-D, one-sided & slippage guards, transfer restrictions, slisBNBx integration, rebalance (incl. out-of-range via inventory conversion), borrow/repay/liquidate.
  • Rate-path (SlisBNBV3ProviderRate.t.sol) — peek/totalAssets/getUserBalanceInBnb invariant to pool manipulation; rate-centered rebalance + drift/deadline/minLiquidity guards. (8 tests)
  • Liquidator (V3Liquidator.t.sol) — pre-funded + flash liquidation, share redemption, token/BNB swaps, whitelist/access control. (27 tests)
  • Reentrancy PoC (V3ProviderReentrancyPoC.t.sol) — test_C1_depositRefundReentrancy_neutralized drives the full attack; fails pre-fix (≈26× inflation, insolvent), passes post-fix.
  • Manual: deploy to testnet, verify deposit/withdraw/rebalance/liquidation end-to-end against a real Lista V3 slisBNB/BNB pool.
forge test --mc SlisBNBV3ProviderTest -vvv
forge test --mc SlisBNBV3ProviderRateTest -vvv
forge test --mc V3LiquidatorTest -vvv
forge test --mc V3ProviderReentrancyPoC -vvv

@razww
razww marked this pull request as draft March 23, 2026 13:20
Comment thread src/provider/V3Provider.sol Outdated
@razww
razww force-pushed the feature/v3-lp branch 2 times, most recently from 04e090b to 7d940c4 Compare March 26, 2026 09:09
@razww
razww marked this pull request as ready for review March 27, 2026 06:39
@razww
razww force-pushed the feature/v3-lp branch 2 times, most recently from 9ff43ab to e1759f8 Compare March 31, 2026 06:17
@razww

razww commented Jun 10, 2026

Copy link
Copy Markdown
Collaborator Author

@audit-agent V3Liquidator.sol V3DexAdapter.sol V3Provider.sol SlisBNBV3DexAdapter.sol SlisBNBV3Provider.sol SlisBNBV3ProviderOracle.sol SlisBnbInventoryLib.sol V3PositionLib.sol

@lista-audit-agent

Copy link
Copy Markdown

🛡️ Cloud-Auditor — PR #151 review

Dispatched: 9 specialists · Head SHA: b48933fd

Tally

Critical High Medium Low Info
0 2 5 9 19

Top findings

H-01 — slisBNBxMinter.rebalance revert bricks deposit / withdraw / liquidation   SlisBNBV3Provider.sol:116-127
V3Provider.liquidate (Moolah callback) → _afterCollateralChange_syncPositionminter.rebalance reverts EXCEED_MPC_CAP when MPC wallets are full. The Moolah liquidation reverts; unhealthy positions cannot be cleared. Inherits PR-188 M-01 without the recommended try/catch. Fix: wrap minter call in try/catch, emit SyncFailed.

H-02 — getUserBalanceInBnb div-by-zero on bnbPrice cascades to full DoS   SlisBNBV3Provider.sol:88
(value0+value1)/bnbPrice with no zero-guard. When slisBNBxMinter is wired, a transient BNB feed dip panics through every deposit/withdraw/liquidate via _syncPosition → minter.rebalance → getUserBalanceInBnb. Sibling code already uses the defensive return 0 pattern. Fix: if (bnbPrice == 0) return 0;.

M-01 — setSlisBNBxMinter does not verify provider-is-registered   SlisBNBV3Provider.sol:106-109
Same as PR-188 M-02. Rotating to a minter that hasn't pre-registered V3Provider bricks every _syncPosition thereafter. Fix: assert moduleConfig(address(this)).moduleAddress == address(this) in the setter.

M-02 — Resilient-oracle TOKEN0/TOKEN1 legs must be rate-derived for the pool-manipulation invariant to hold
Composition is rate-based (manipulation-resistant) but per-token USD prices come from resilientOracle. A DEX-based slisBNB feed re-opens the inflation vector closed by bailsec smart_provider Issue_07. Deploy runbook requirement; optionally enforce on-chain.

M-03 — centerRateThresholdBps MANAGER misconfig can permanently block rebalance
Range 0..10000 BPS; slisBNB rate moves ~4%/yr. Any setting above ~200 BPS makes rebalance unreachable. Fix: cap at ~500 BPS.

M-04 — V3Liquidator.NoProfit checks balanceOf < repaidAssets, not delta
A pre-existing loanToken balance silently covers swap paths producing zero output — amplifies BOT-compromise blast radius. Fix: balance-delta check or sweep between liquidations.

M-05 — peek ZeroPrice revert bricks liquidation while either resilient-oracle leg is down (intentional; document, monitor heartbeats)

Lows (9)

L-01 Missing __gap in SlisBNBV3Provider, SlisBNBV3DexAdapter, V3Liquidator (PR-188 L-09 pattern) ·
L-02 SmartProvider.setSlisBNBxMinter zero-guard removed (regression) ·
L-03 V3DexAdapter.setProvider irreversible — wrong wiring traps NFT + idle ·
L-04 ERC-4626 max* not overridden (spec break, PR-188 M-01 secondary) ·
L-05 V3Provider.liquidate / syncUserBalance missing nonReentrant (defense-in-depth) ·
L-06 V3Liquidator.withdrawERC20/ETH lets MANAGER drain (acknowledged centralization) ·
L-07 _sqrtPriceX96FromRate(0) silently misprices when rate == 0 ·
L-08 Base V3DexAdapter.fairSqrtPriceX96 reverts when pool observationCardinality == 1 (deploy checklist) ·
L-09 WBNB-refund call reverts for non-payable receivers (document).

Acknowledged / verified safe

  • C-1 deposit-refund reentrancy fix verified (PoC + call-graph trace).
  • First-depositor / donation inflation structurally blocked by storage-tracked idle accounting.
  • Pool spot manipulation of share price neutralized by StakeManager-rate fair sqrt price.
  • BOT trust on swap calldata: cites bailsec-lista_lending-2025-04-10 Issue_29 (acknowledged).
  • DEFAULT_ADMIN_ROLE renounce: cites cantina-credit_loan-2026-02-04 §3.3.2 (Timelock).

Full report (PDF) delivered to the security Telegram group.

@hashdit-bot

hashdit-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Pull Request Review

This PR introduces a new V3 collateral-provider architecture for concentrated liquidity positions by adding multiple new Solidity contracts: a generic V3Provider vault, V3DexAdapter strategy/custody layer, standalone V3ProviderOracle, chain/pair-specific variants (slisBNB, wstETH, wbETH), and a new V3Liquidator. It also updates build configuration/remappings and adds extensive fork-based tests for BSC and Ethereum covering deposits/withdrawals, rebalancing, liquidation paths, and reentrancy regression behavior. Additionally, there is a small change in SmartProvider.setSlisBNBxMinter validation logic (zero address check replaced by same minter check).

Sensitive Content

Blockchain Address:

  • 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE (native token sentinel) in src/liquidator/V3Liquidator.sol, src/provider/v3/SlisBNBV3Provider.sol, tests
  • 0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B (slisBNB) in src/provider/v3/SlisBNBV3DexAdapter.sol, src/provider/v3/SlisBNBV3ProviderOracle.sol, tests
  • 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c (WBNB) in src/provider/v3/SlisBNBV3DexAdapter.sol, src/provider/v3/SlisBNBV3ProviderOracle.sol, tests
  • 0x1adB950d8bB3dA4bE104211D5AB038628e477fE6 (StakeManager) in src/provider/v3/SlisBNBV3DexAdapter.sol, tests
  • 0xa2E3356610840701BDf5611a53974510Ae27E2e1 (wbETH) in src/provider/v3/WbETHV3DexAdapter.sol
  • 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 (WETH) in src/provider/v3/WbETHV3DexAdapter.sol, src/provider/v3/WstETHV3DexAdapter.sol
  • 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 (wstETH) in src/provider/v3/WstETHV3DexAdapter.sol

Security Issues

🟡 [MEDIUM] Input validation relaxed in setSlisBNBxMinter (confirm intended behavior)

File: src/provider/SmartProvider.sol
The setter changed from rejecting zero address (require(_slisBNBxMinter != address(0), ...)) to only rejecting setting the same value. This now allows setting slisBNBxMinter to address(0), which alters prior access/operational assumptions and may disable rebalancing hook integrations unexpectedly if not intentional.
Recommendation: Confirm this is intended as a feature (explicit disable path). If yes, consider documenting it clearly and emitting a distinct event/guardrail for disabling; if not, restore the non-zero check.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

@hashdit-bot

hashdit-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Pull Request Review

This PR introduces a new V3 concentrated-liquidity provider architecture for slisBNB/WBNB by adding multiple new Solidity components: V3Provider (vault), V3DexAdapter (NFT/pool custodian), SlisBNBV3ProviderOracle (share oracle), and V3Liquidator (liquidation flows), along with supporting interfaces/libraries and extensive fork-based tests. It also updates project dependencies/remappings to include lista-v3 and shared lista-dao-contracts libs. Additionally, SmartProvider.setSlisBNBxMinter was changed to allow zero-address unsetting while preventing no-op updates (same minter).

Sensitive Content

Blockchain Address:

  • 0xbb4C...095c (BSC WBNB) in multiple production files (e.g., src/provider/V3Provider.sol, src/provider/V3DexAdapter.sol, src/liquidator/V3Liquidator.sol) — hardcoded chain token constant
  • 0xB0b8...4A1B (slisBNB) in multiple production files (e.g., src/provider/SlisBNBV3DexAdapter.sol, src/provider/SlisBNBV3ProviderOracle.sol) — hardcoded pair token constant
  • 0x1adB...7fE6 (StakeManager) in src/provider/SlisBNBV3DexAdapter.sol — hardcoded external protocol dependency
  • 0xEeee...EEeE (native BNB sentinel address) in src/provider/SlisBNBV3Provider.sol and src/liquidator/V3Liquidator.sol — native token placeholder-style runtime constant

Security Issues

No serious security issues detected.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

@hashdit-bot

hashdit-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Pull Request Review

This PR introduces a major new Solidity web3 architecture for V3 concentrated-liquidity collateral: a split between V3Provider (vault/share token), V3DexAdapter (NFT + pool custody/logic), and V3ProviderOracle, plus a new V3Liquidator for pre-funded and flash liquidation flows. It adds slisBNB/BSC and wstETH/wbETH Ethereum specializations, new inventory/position libraries, and extensive fork-based tests for providers, rate invariance, liquidator behavior, and reentrancy regression. It also updates project dependencies/remappings (including lista-v3) and adjusts one existing SmartProvider minter setter check.

Sensitive Content

Blockchain Address:

  • 0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B in src/provider/v3/SlisBNBV3DexAdapter.sol — hardcoded slisBNB token constant
  • 0x1adB950d8bB3dA4bE104211D5AB038628e477fE6 in src/provider/v3/SlisBNBV3DexAdapter.sol — hardcoded StakeManager constant
  • 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c in src/provider/v3/SlisBNBV3DexAdapter.sol — hardcoded WBNB constant
  • 0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B in src/provider/v3/SlisBNBV3ProviderOracle.sol — hardcoded slisBNB pair guard
  • 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c in src/provider/v3/SlisBNBV3ProviderOracle.sol — hardcoded WBNB pair guard
  • 0xa2E3356610840701BDf5611a53974510Ae27E2e1 in src/provider/v3/WbETHV3DexAdapter.sol — hardcoded wbETH constant
  • 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 in src/provider/v3/WbETHV3DexAdapter.sol — hardcoded WETH constant
  • 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 in src/provider/v3/WstETHV3DexAdapter.sol — hardcoded wstETH constant
  • 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 in src/provider/v3/WstETHV3DexAdapter.sol — hardcoded WETH constant

No sensitive key material or social account handles/links detected in non-test added files.

Security Issues

No serious security issues detected.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

@hashdit-bot

hashdit-bot Bot commented Jun 26, 2026

Copy link
Copy Markdown

Pull Request Review

This PR introduces a new V3 concentrated-liquidity provider architecture for Moolah collateral by adding a large set of Solidity contracts: V3Provider (vault shares + Moolah wiring), V3DexAdapter (sole NFT/liquidity custodian), V3ProviderOracle (share pricing), plus chain/pair specializations for slisBNB, wstETH, and wbETH, and a new V3Liquidator. It also updates project dependencies/remappings (including lista-v3) and adds extensive fork-based integration/regression tests across BSC and Ethereum. Additionally, an existing check in SmartProvider.setSlisBNBxMinter was changed from a non-zero guard to a “not same as current” guard.

Sensitive Content

Blockchain Address:

  • 0xB0b84D294e0C75A6abe60171b70edEb2EFd14A1B in src/provider/v3/SlisBNBV3DexAdapter.sol / src/provider/v3/SlisBNBV3ProviderOracle.sol — hardcoded slisBNB token address
  • 0x1adB950d8bB3dA4bE104211D5AB038628e477fE6 in src/provider/v3/SlisBNBV3DexAdapter.sol — hardcoded StakeManager address
  • 0xbb4CdB9CBd36B01bD1cBaEBF2De08d9173bc095c in src/provider/v3/SlisBNBV3DexAdapter.sol / src/provider/v3/SlisBNBV3ProviderOracle.sol — hardcoded WBNB address
  • 0xa2E3356610840701BDf5611a53974510Ae27E2e1 in src/provider/v3/WbETHV3DexAdapter.sol — hardcoded wbETH address
  • 0xC02aaA39b223FE8D0A0e5C4F27eAD9083C756Cc2 in src/provider/v3/WbETHV3DexAdapter.sol / src/provider/v3/WstETHV3DexAdapter.sol — hardcoded WETH address
  • 0x7f39C581F595B53c5cb19bD0b3f8dA6c935E2Ca0 in src/provider/v3/WstETHV3DexAdapter.sol — hardcoded wstETH address
  • 0xEeeeeEeeeEeEeeEeEeEeeEEEeeeeEeeeeeeeEEeE in src/liquidator/V3Liquidator.sol and provider contracts — native-token sentinel address

Security Issues

🟡 [MEDIUM] Input validation relaxed in setSlisBNBxMinter may allow zero address assignment

File: src/provider/SmartProvider.sol
The function changed from require(_slisBNBxMinter != address(0), "zero address provided"); to require(_slisBNBxMinter != slisBNBxMinter, "same minter");, which now permits setting the minter to address(0). This may be intentional (to disable minter), but it is an access/authorization-related behavior change that could alter downstream assumptions and disable rebalancing hooks unexpectedly.
Recommendation: Please confirm this is intended. If zero should be disallowed, restore explicit non-zero validation; if zero is a supported “disable” state, document it clearly and emit/handle it consistently across dependent flows.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

@hashdit-bot

hashdit-bot Bot commented Jul 21, 2026

Copy link
Copy Markdown

Pull Request Review

This Solidity DeFi PR introduces an upgradeable V3 concentrated-liquidity collateral system comprising ERC-4626 provider vaults, DEX/NFT adapters, share-price oracles, and a dedicated liquidation contract for BSC and Ethereum LST pairs. It also adds rate-centered rebalancing, native-token handling, manipulation-resistant valuation, extensive fork tests, and a small SmartProvider minter-validation change.

Sensitive Content

No sensitive content detected.

Security Issues

🟠 [HIGH] Pre-existing native reserves can satisfy wrapped-native flash-liquidation repayment

File: src/liquidator/V3Liquidator.sol
When the loan token is the wrapped-native token, onMoolahLiquidate wraps the contract’s entire native balance after snapshotting only its ERC-20 loan-token balance. Consequently, native ETH/BNB held before the liquidation is converted into WETH/WBNB and counted as proceeds from the current liquidation, allowing an unprofitable liquidation to pass the out >= repaidAssets check and consume unrelated reserves. This bypasses the delta-based reserve-protection logic already applied to ERC-20 balances.
Recommendation: Snapshot the native balance before redemption and wrap only the native amount produced during the current callback, or include the wrapped value of the pre-existing native balance in the initial baseline. Add a regression test with pre-funded native reserves and an otherwise insufficient wrapped-native liquidation.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

raz-w-20230331 and others added 10 commits July 29, 2026 16:19
… fork

Remove scattered LICENSE files under dex/v3/core/ and add a single NOTICE
file with proper attribution. Transition expired BUSL-1.1 headers to
GPL-2.0-or-later and fix GPL-3.0 incompatibility in periphery libraries.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
Remove the ~9.4k-LoC local fork under src/dex/v3 (a Uniswap-v3 fork
ported to 0.8.34) and depend on github.com/lista-dao/lista-v3 instead.

lista-v3 master is Solidity 0.7.6, so it cannot be compiled into this
0.8.34 codebase wholesale. Split by what actually compiles under 0.8.34:

- Pure interfaces (IListaV3Factory, IListaV3Pool, pragma >=0.5.0) now
  come from the lib/lista-v3 submodule via the `lista-v3/` remapping.
- The V3 math libs V3Provider executes internally (TickMath, SqrtPriceMath,
  LiquidityAmounts, FullMath, FixedPoint96, SafeCast, UnsafeMath) are
  0.7.6 upstream and need 0.8.x unchecked semantics, so the existing
  0.8.34 ports are relocated to src/provider/libraries/.
- INonfungiblePositionManager: the submodule's interface inherits OZ
  ERC721-upgradeable interfaces whose v4-era paths/names don't exist in
  this repo's OZ v5.2, so a minimal self-contained NPM interface (only
  the methods V3Provider calls) lives in src/provider/interfaces/.

Delete test/dex/v3/ListaV3.t.sol (tested the fork implementation itself;
the DEX is tested in the lista-v3 repo) and repoint the IListaV3Pool
import in V3Provider/V3Liquidator tests to the submodule.

The unrelated StableSwap DEX under src/dex/*.sol is untouched.
forge build: compiler run successful.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
Replace the 7 locally-vendored 0.8.34 math-lib ports under
src/provider/libraries/ with the audited, 0.8-native libraries from the
lista-dao-contracts submodule (pinned at 3ac9ef2, matching master).

- Register lib/lista-dao-contracts.git as a submodule + `lista-dao-contracts/`
  remapping. The entry mirrors origin/master exactly, so it is conflict-free
  when this branch later integrates with master.
- V3Provider imports TickMath and LiquidityAmounts from the submodule.
- _getAmountsForLiquidity now delegates to LiquidityAmounts.getAmountsForLiquidity,
  which is mathematically identical to the previous SqrtPriceMath.getAmount{0,1}Delta
  (roundUp=false) path. This removes the need for SqrtPriceMath (which
  lista-dao-contracts does not ship a 0.8 version of) and its deps.
- Delete all 7 local ports: TickMath, SqrtPriceMath, LiquidityAmounts, FullMath,
  FixedPoint96, SafeCast, UnsafeMath.

Verified: forge build clean; 84 V3Provider + 27 V3Liquidator tests pass
(covering preview/withdraw/rebalance across below/inside/above-range, which
exercises the swapped getAmountsForLiquidity path).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…-rate oracle

Rename V3Provider → SlisBNBV3Provider and split into a generic abstract base
(V3Provider) + slisBNB/BNB specialization, per the slisBNB/BNB v3 LP PRD.

- Base/derived split via virtual hooks (_afterCollateralChange,
  _rebalanceInventory, _valuationSqrtPriceX96, peek/getTokenConfig/receive).
- ERC-4626 shell: asset = WBNB, totalAssets() in BNB; single-asset entry
  disabled (two-token deposit/withdraw/redeemShares + withdrawShares/supplyShares).
- Exchange-rate oracle (PRD §4.5): peek/totalAssets/getUserBalanceInBnb value the
  position at the slisBNB exchange-rate-implied price (StakeManager
  convertSnBnbToBnb/convertBnbToSnBnb), never the pool spot/TWAP — manipulation-resistant.
- Auto-centered range: initialize derives ticks from exchangeRate ±1%; rebalance
  recenters and converts inventory via the StakeManager (deposit / instantWithdraw).
- Libraries: V3PositionLib (NPM primitives) + SlisBnbInventoryLib (stake/redeem +
  optimal-ratio conversion); audited 0.8 math from lista-dao-contracts.
- require strings → custom errors; AccessControl (non-enumerable).
- Tests: SlisBNBV3Provider.t.sol (USDC/WBNB generic) + SlisBNBV3ProviderRate.t.sol
  (slisBNB/WBNB rate-path, forked).

Note: runtime bytecode currently exceeds EIP-170; size-reduction refactor pending.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…+ oracle

Split the monolithic SlisBNBV3Provider (28.5 KB, over EIP-170) into three UUPS
contracts, each well under the limit, with clean responsibility boundaries:

- V3Provider / SlisBNBV3Provider: ERC-4626 vLP shares + Moolah wiring + share
  accounting (holds no NFT); slisBNB subclass adds slisBNBx mirroring + BOT rebalance.
- V3DexAdapter / SlisBNBV3DexAdapter: sole V3 NFT/idle custodian + all NPM/pool math;
  slisBNB subclass adds the exchange-rate-implied fair price, +/-1% tick centering,
  and the rate-centered rebalance + StakeManager inventory conversion.
- SlisBNBV3ProviderOracle: Moolah market.oracle; prices the share off the adapter's
  fair composition (manipulation-resistant) + resilient oracle, capped haircut.

Key points:
- CEI: burn shares BEFORE adapter.removeLiquidity pushes underlying to the receiver,
  so totalSupply stays consistent with the reduced position during the BNB callback.
- Oracle reverts on zero leg price / zero total value (finding D); raw-NAV vs haircut
  split is clean (adapter returns raw, oracle applies the only haircut).
- slisBNB/BNB-only: adapter and oracle constructors reject any non-slisBNB/WBNB pair;
  the oracle also asserts its tokens match the adapter's (no wiring divergence).
- IV3PoolMinimal: decode only slot0 sqrtPriceX96/tick, width-agnostic to feeProtocol
  (Uniswap uint8 vs PancakeSwap uint32) so the adapter works against any V3 fork.
- Tests retargeted to the slisBNB/WBNB pool: functional 87, rate-path 8, liquidator 27
  (etched StakeManager stand-in for the not-yet-deployed instantWithdraw).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…matting

- foundry.lock: remove orphaned lib/v3-core and lib/v3-periphery entries
  (superseded by lista-v3; no .gitmodules entry, lib dir, remapping, or import).
- Apply prettier formatting to the V3 vault/adapter contracts and the
  liquidator + rate-path tests (npm run check now passes).

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
razww and others added 23 commits July 29, 2026 16:20
…tracts

Switch V3Provider, V3DexAdapter and SlisBNBV3ProviderOracle from plain
AccessControlUpgradeable to AccessControlEnumerableUpgradeable, matching the
codebase standard (Moolah, SmartProvider, et al.) so roles are enumerable
on-chain via getRoleMember/getRoleMemberCount. Existing role logic
(grantRole/hasRole/onlyRole/getRoleAdmin) is unchanged; all three stay under EIP-170.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…quidator

Match the V3 provider contracts and the codebase standard so roles are
enumerable on-chain (getRoleMember/getRoleMemberCount). Existing role logic is
unchanged; V3Liquidator stays well under EIP-170.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
V3Provider.deposit forwarded the unused-input refund to the depositor via a
native-BNB call inside adapter.addLiquidity, BEFORE _mint + supplyCollateral.
During that callback the adapter NAV already included the freshly-added
liquidity while totalSupply() was still stale, so SlisBNBV3ProviderOracle.peek
returned a transiently inflated share price (26x on a BSC fork). Moolah was not
yet reentrancy-locked at that point, letting a malicious depositor reenter
Moolah.borrow and over-borrow against pre-existing collateral, leaving bad debt.

Fix: the adapter now refunds to the vault, and the vault forwards the refund to
the depositor only AFTER _mint + supplyCollateral, when NAV and totalSupply are
consistent. Adds a guarded receive() (adapter-only) + _refund helper.

Regression: test_C1_depositRefundReentrancy_neutralized drives the full attack
and asserts peek during the refund equals the normal price and the attacker
position stays solvent. Fails pre-fix (26x inflation, insolvent), passes post-fix.

Co-Authored-By: Claude Fable 5 <noreply@anthropic.com>
…ETH/wbETH

Generalize the slisBNB V3 LP provider into a chain/LST-agnostic base and add
Ethereum wstETH/WETH and wbETH/WETH markets.

- Base: chain/LST-agnostic V3Provider / V3DexAdapter / V3ProviderOracle
  (WRAPPED_NATIVE immutable; virtual _lstNativeRate() / _convertToOptimalRatio()
  hooks). slisBNB becomes a thin, byte-equivalent subclass. All V3 contracts
  relocated under src/provider/v3/.
- New LST families: wstETH/WETH and wbETH/WETH adapters + vaults + IWstETH/IWbETH.
- Oracle: rate-implied (slisBNB) / pool-TWAP-clamped-to-rate (wstETH/wbETH) share
  pricing; components valued rate-derived via the resilient oracle.
- Rebalance swap is DEX-agnostic: backend-built swapData against a whitelisted
  swapPair (SwapInventoryLib generic swap, backend amountOutMin); drop SwapRouter02
  coupling (delete ISwapRouter02).
- V3Liquidator: chain-agnostic native leg via provider.WRAPPED_NATIVE() for both
  legs; wrap native back when loanToken == wrapped-native; reject sensitive
  swapPair (token/pool/NPM).
- Cross-validate provider<->adapter<->oracle wiring in setProvider + oracle ctor.

Tests: 245 passing — BSC slisBNB/liquidator regression unchanged + Ethereum fork
suites for wstETH, wbETH and V3Liquidator (incl. native-leg + wiring guards).

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…onversion

Add maxInstantWithdrawSlippageBps to SlisBnbInventoryLib so the slisBNB→BNB
instantWithdraw leg of a rebalance enforces a bound on the StakeManager
instant-redeem fee/slippage, reverting InstantWithdrawSlippage when exceeded.
Threaded through convertToOptimalRatio/convert/_convert; SlisBNBV3DexAdapter
passes the configured bound. Adds tests.

Co-Authored-By: Claude Opus 4.8 (1M context) <noreply@anthropic.com>
…nostic swap)

Make the slisBNB/BNB rebalance inventory conversion identical to the wstETH/WETH and
wbETH/WETH families: a backend-built swap against a whitelisted venue, swap-pair-agnostic on
chain. instantWithdraw is conceptually just a swap, so the StakeManager special-casing is no
longer needed — promoting one shared implementation also simplifies the code.

- Promote the generic conversion to the base V3DexAdapter: swapPairWhitelist +
  setSwapPairWhitelist (rejecting TOKEN0/TOKEN1/POOL/NPM) + the decode-and-swap
  _convertToOptimalRatio (via SwapInventoryLib, backend amountOutMin). All three rate-implied
  subclasses inherit it; both declared on IV3DexAdapter.
- SwapInventoryLib is native-aware: when a venue settles the wrapped-native leg as the native
  coin (e.g. a StakeManager instant-redeem → BNB), the received native is wrapped back into the
  wrapped-native ERC20 before amountOutMin is measured; receive() accepts native from a
  whitelisted venue. ⇒ instantWithdraw works as a whitelisted swap venue (slisBNB→BNB→WBNB),
  with no StakeManager special-casing on chain.
- Slim SlisBNBV3DexAdapter to _lstNativeRate() (StakeManager rate) + the pair guard; drop the
  StakeManager conversion override, the instant-withdraw slippage config, and the StakeManager
  receive() override. Delete SlisBnbInventoryLib.
- Slim WstETH/WbETHV3DexAdapter: swap logic now in the base; keep rate hook + TWAP-clamp.
- SlisBNBV3Provider.rebalance gains bytes calldata swapData, forwarded to the adapter.

Behavioral change: slisBNB rebalance converts via a whitelisted venue (a DEX/aggregator OR the
StakeManager instant-redeem) using backend swapData + amountOutMin, instead of a hardcoded path.

Tests: 257 passing — slisBNB swap-conversion (instantWithdraw-as-venue, both directions,
amountOutMin-revert) + whitelist + sensitive-pair-reject; old StakeManager-special-case/slippage
tests removed; wstETH/wbETH/liquidator/reentrancy green.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Build on the unified DEX-agnostic rebalance swap (9ba9766) so the V3 LP base is correct for
native-settling venues and non-18-decimal tokens.

- SwapInventoryLib native-aware both directions: the swapData blob gains bool nativeIn — now
  (swapPair, sellToken0, amountIn, amountOutMin, nativeIn, innerSwapData). nativeIn ⇒ the wrapped-native
  input leg is unwrapped and forwarded as msg.value (supports StakeManager.deposit{value} for
  WBNB→slisBNB). Any native a venue delivers (e.g. instantWithdraw → BNB) is wrapped back into the
  wrapped-native ERC20 before amountOutMin is measured — never stranded; reverts UnexpectedNative if
  neither leg is the wrapped-native.
- Non-18-decimal paired token: _sqrtPriceX96FromRate now scales the raw pool price by
  10^(DECIMALS1 - DECIMALS0) instead of assuming equal decimals (pure→view; cascaded to _tickRangeForRate).
  The wrapped-native stays 18-dec; the paired token may have any decimals.
- Oracle share-decimal correctness: V3ProviderOracle quotes peek(share) per ONE WHOLE share
  (10 ** SHARE_DECIMALS, read from the share token) instead of hardcoded 1e18, matching Moolah._getPrice
  which uses collateralToken.decimals(). No-op for the current 18-dec accounting assets.

Tests: 263 passing — new V3DexAdapterDecimals.t.sol (non-18-dec USDC(6)/WETH(18) + 18/18) proves the
sqrtPrice decimal math; slisBNB depositAsSwapVenue (nativeIn), wrongNativeOutput_reverts, instantWithdraw
no-stranded-native; swapData blobs updated to the 6-field shape. Backend encoder must add nativeIn.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…per-rebalance + daily caps)

Add three independent loss guards to the V3 LP collateral provider's rebalance so a
compromised BOT (hot key controlling swapData / minAmount / minLiquidity / deadline)
cannot bleed the managed position:

- Per-swap rate floor (V3DexAdapter._convertToOptimalRatio): the inventory-conversion
  swap must return >= fairAtRate(spent) * (LOSS_DENOM - maxSwapLossBp) / LOSS_DENOM,
  measured against the LST<->native rate (not pool spot, not the BOT's amountOutMin).
  maxSwapLossBp in ppm, default 5%.
- Per-rebalance fair-NAV neutrality (V3Provider._guardedRebalance): the position's fair
  USD value (never pool spot) may not drop more than maxRebalanceLossBp (ppm, default 2%)
  across one rebalance. Fail-closed (OracleZero) when shares exist but NAV reads 0.
- Per-UTC-day cumulative loss cap: loss-only accumulator, reverts past maxDailyLossUsd
  (default 1000e8).

All caps are MANAGER-settable and bounded, and are emitted on init and on change. The
slisBNB / wstETH / wbETH vaults route rebalance() through _guardedRebalance; IV3DexAdapter
gains maxSwapLossBp() / setMaxSwapLossBp. UUPS storage gaps shrunk (adapter 47->46,
provider 50->46) to keep subclass layout stable.

Tests: per-swap cap (revert / within-cap pass / sell-token1), NAV neutrality (revert +
zero-price fail-closed), daily cap (single-rebalance revert + same-day accumulation +
next-day reset), defaults, setter access/caps, and wstETH guard inheritance.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Compounding accrued V3 fees added liquidity at the pool's current spot
price with zero slippage protection (amountMin = 0). Since this runs as
a side effect of deposit/withdraw/redeemShares/rebalance, a flash-loan
that skews the pool spot could force the vault to mint liquidity at a
manipulated price and get sandwiched on the reverting back-swap.

V3DexAdapter now compares spot against the rate-anchored fair price
before re-adding compounded fees to the position. When the deviation
exceeds a manager-configurable threshold it skips the re-add (holds the
fees as idle, valued at fair) instead of reverting, so the hot-path
deposit/withdraw call still succeeds. V3Provider also exposes a
permissionless compound() so fees can be swept on a regular cadence
instead of only as a side effect of user activity.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
… profitability

V3Liquidator.onMoolahLiquidate checked profitability with an absolute
balance comparison (loanToken.balanceOf(this) < repaidAssets), which
never separates loan token this liquidation generated from loan token
the contract already held from unrelated prior activity. An
unprofitable flash liquidation could pass by drawing down that
pre-existing reserve, silently depleting it while reporting success.

Snapshot the loanToken balance before redeeming shares and swapping,
then require the increase over that snapshot to cover repaidAssets,
matching the delta-based check already used in Liquidator.sol and
BrokerLiquidator.sol.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…on one-sided idle

Two related collateral-accounting fixes on the V3 provider.

1) Deposit share issuance no longer routes through a pool-spot mint that is
then re-valued at the fair price. Previously a subsequent deposit added
liquidity at the pool spot ratio but derived shares from the fair-price
re-decomposition of the minted liquidity; when spot and fair diverged and
idle inventory was present, the depositor was over-credited shares and
could withdraw pro-rata for a net gain at existing holders' expense.

The subsequent-deposit path now issues shares purely proportional to the
existing position composition (snapshotted at the fair price, inclusive of
idle and collected fees) and parks the consumed tokens as idle inventory
instead of minting into the pool. The pool spot price never enters share
issuance, so the deposit is value-conserving and cannot dilute existing
holders. Parked idle is deployed into the pool later by the permissionless
compound() (keeper cadence) or the next deposit/withdraw, always spot-gated.
As a consequence, a deposit must now match the (two-sided) fair composition;
a one-sided deposit binds the composition fraction to the empty leg and
reverts with ZeroShares. The first-deposit path is unchanged (it opens the
position and has no existing holders to dilute). amount0Min/amount1Min are
reinterpreted as the minimum amount of each leg consumed.

2) collectAndCompound now no-ops on zero addable liquidity. When idle
inventory is one-sided relative to the active range, the computed liquidity
is zero and the underlying pool mint reverts, which would brick deposit,
withdraw, redeemShares and the permissionless compound(). The adapter now
holds the inventory as idle and returns instead of reverting.

Adds a creditIdle(amount0, amount1) adapter entrypoint (onlyProvider) for the
park-to-idle deposit path, and relaxes the provider receive() to also accept
native from the wrapped-native token (the vault unwraps a WBNB leftover
before refunding it as native). Provider suite: 111 passing, including two
new counter-tests covering both fixes.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
Adds a caller-specified share-slippage floor to V3Provider.deposit: the call
now takes a minShares argument and reverts with InsufficientShares when the
minted shares fall below it. This protects a depositor from being silently
under-credited — a first-depositor inflation attack that inflates the
position via a direct NPM.increaseLiquidity donation, or the fair
composition shifting between the off-chain preview and on-chain execution.
Passing 0 disables the check. The IV3Provider interface and all deposit call
sites are updated for the new parameter.

Also adds two view helpers so front-ends can size the two deposit legs to the
current fair composition (minimising the refund under the proportional
deposit path):
- getFairComposition() returns (T0, T1) valued at the fair price, and
- previewDepositForToken0/previewDepositForToken1 return the matching amount
  of the other leg at that ratio.

Provider suites pass (299 tests), including a new counter-test asserting the
minShares floor reverts an under-crediting deposit and passes at an
achievable floor. SlisBNBV3Provider runtime size is unchanged (~23.1KB, well
within the 24KB limit). The operational first-deposit seed that complements
this guard is documented in the launch runbook.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
positionAmountsAt only added the tokensOwed checkpointed at the position's
last NPM poke (mint/increase/decrease/collect), ignoring fees earned in the
pool since then. Because this view underpins every valuation — totalAssets,
the provider-share oracle peek, Moolah health checks and liquidations, and
slisBNBx balances — collateral was systematically under-valued between fee
checkpoints, causing premature liquidations and over-burnt slisBNBx.

positionAmountsAt now adds the live uncollected fees, simulated from the
position's feeGrowthInside deltas exactly as Uniswap V3's Tick.getFeeGrowthInside
and Position.update compute them (all fee-growth arithmetic wraps by design,
in unchecked blocks). The pending amount uses the pool's current tick and
cumulative fee growth — pool facts that only ever correct a stale under-count,
not spot-price-manipulable for value extraction.

IV3PoolMinimal is extended with feeGrowthGlobal0X128/1X128 and a ticks() reader
that decodes only the first four (fixed-width) fields, staying width-agnostic
across V3 flavors like the existing slot0 reader.

A fork test cross-checks the simulated pending against the pool's ground truth:
NPM.collect (which pokes the pool) returns checkpointed owed plus freshly
earned, so collected minus owed equals the simulated pending to the wei. Two
rebalance tests that had used the (now fee-inclusive) full position value as a
decreaseLiquidity principal floor are corrected to size that floor from the
liquidity principal at spot; the minAmount semantic is unchanged. All V3 suites
pass. The change lands entirely in the adapter; the provider bytecode is
unchanged.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…red amount

removeLiquidity forwarded the caller's minAmount0/1 to decreaseLiquidity, whose
slippage check covers only the principal burned from the pool. The pro-rata
idle inventory is added afterward, so the amount actually delivered to the
receiver is principal + fees + idle. Callers size minAmount from
previewRemoveLiquidity, which reports principal + pro-rata idle, so a withdraw
or redeem reverted with the pool's slippage error whenever idle was a
meaningful share of the payout — and, on an idle-only or dust exit
(liquidityToRemove rounds to 0), no floor was enforced at all.

decreaseLiquidity is now called with zero mins and the floor is checked once
against the total delivered amount (principal + fees + idle), reverting with
InsufficientAmount. This matches what previewRemoveLiquidity reports, removes
the spurious reverts, and covers the idle-only/dust path. A counter-test drives
a one-sided-idle redeem (so the pre-removeLiquidity compound no-ops and the
idle survives) with a preview-sized floor: it reverted before, passes now. All
V3 suites pass (303 tests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
_twapTick computed the cumulative-tick delta and its division under Solidity
0.8 checked math, deviating from Uniswap's OracleLibrary.consult, which was
compiled under <0.8 and relies on the int56 subtraction wrapping. A wrapped
tickCumulatives delta would therefore revert here instead of producing the
reference tick. Wrap the delta/division/modulo in an unchecked block to match
the reference. Behavior is unchanged for all realistic observations (the wrap
is astronomically far off); this only removes the spurious-revert deviation.
All TWAP-using suites pass (153 tests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
rebalance enforced its minLiquidity floor only against the newly-minted
liquidity, which can be satisfied by liquidity minted at a price manipulated
around the call (then sandwiched on the attacker's back-swap). rebalance now
takes a targetSqrtPriceX96 — the pool price the backend built the rebalance
against — and, after the swap and re-mint, requires the actual pool spot to be
within maxSpotDeviationBps of it, reverting with SpotDeviationTooHigh
otherwise. A pool moved off the BOT's expectation therefore reverts instead of
minting off-market liquidity. targetSqrtPriceX96 == 0 opts out (parity with the
other caller-supplied guards) and maxSpotDeviationBps == 0 disables the gate.

The new argument threads through IV3DexAdapter.rebalance, the adapter, the
provider _guardedRebalance wrapper, and the three provider BOT entrypoints
(slisBNB / wbETH / wstETH). Generalizes the existing spot-vs-fair comparison
into a reusable _priceWithinDeviation helper. Adds seven rebalance target-price
tests (opt-out, gate-disabled, low/high-side, band boundary, sandwich-with-fair
target, matching-target pass). All V3 suites pass (307 tests).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ap legs

SwapInventoryLib.swap wrapped any native-balance increase after the venue call
back into the wrapped-native token, and reverted (UnexpectedNative) when
neither swap leg was the wrapped-native token. A venue that forwards its native
balance (e.g. a router that sweeps stray native — seedable by an attacker with
a 1-wei donation) could therefore brick an ERC-20<->ERC-20 conversion on a pair
where neither token is the wrapped-native.

The re-wrap now runs only when a swap leg IS the wrapped-native token; when
neither is, any native that appears is an unsolicited donation outside this
swap's accounting and is left untouched rather than reverting. The legitimate
native-out (proceeds) and native-in (unspent refund) paths are unchanged, and
the nativeIn input guard still uses UnexpectedNative.

Live pairs (slisBNB/WBNB, wstETH/WETH) always include the wrapped-native leg,
so this only affects future non-native pairs; a delegatecall-harness test
exercises the neither-leg path (a stray-native donation no longer reverts).

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…ation

- V3ProviderOracle.initialize now emits HaircutChanged(_haircutBps) so the
  initial haircut is observable from events alone.
- setHaircutBps returns early when the value is unchanged, avoiding a redundant
  storage write and a misleading no-op HaircutChanged event.
- SlisBNBV3DexAdapter: the constructor already pins TOKEN0 == slisBNB and
  TOKEN1 == WBNB, so the always-true _isSlisBnbWbnbPool guard and the dead
  branch in _poolPriceRate are removed; _lstNativeRate returns
  STAKE_MANAGER.convertSnBnbToBnb(1e18) directly.

Co-Authored-By: Claude Sonnet 5 <noreply@anthropic.com>
…compound hardening

- Compound accrued fees before the Moolah health-check valuation in
  withdraw / withdrawShares / supplyShares, so the position is valued
  with fees folded in rather than transiently under-valued.
- Fail closed on a zero oracle leg: V3Provider._amountsValueUsd and
  SlisBNBV3Provider.getUserBalanceInBnb now revert OracleZero when any
  returned price is 0 (prevents a transient-zero feed silently burning a
  user's reward balance toward 0).
- transferFrom override now debits the granted allowance via
  _spendAllowance before moving shares, instead of transferring on an
  unbounded allowance.
- Compounded event now reports the amounts actually consumed by
  increaseLiquidity (used0/used1), not the raw pre-leftover inputs.
- Clamp the initial tick range to the usable [MIN_TICK, MAX_TICK] band
  after floor/ceil alignment.
- Short-circuit a pure no-op recenter (range unchanged and no swap,
  target, or min floors supplied) to an in-place compound, avoiding an
  unnecessary burn/mint cycle; guarded / swap paths still run in full.

Adds counter-tests for the allowance-checked transfer and the no-op
recenter boundary; updates the recenter tests to assert compound-in-place.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
flashLiquidate hardcoded the amount0 / amount1 fields of the
V3Liquidation event to 0. The real redeemed amounts are only known
inside the onMoolahLiquidate callback (redeemShares' return values),
which runs during Moolah's liquidate call, so they never reached the
emit site.

Capture the redeemed leg amounts in the callback and pass them back to
flashLiquidate via appended (upgrade-safe) storage, zeroed before each
liquidate so a non-redeeming flash liquidation still reports 0.

Adds a counter-test asserting both event amounts are non-zero after a
redeem-and-swap flash liquidation.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…st floor

- removeLiquidity reverts ZeroAmount when a share-burning redeem would
  deliver (0,0), instead of silently burning the caller's shares.
- SwapInventoryLib.swap fails fast with InsufficientInventory when the
  requested amountIn exceeds the position's balance, rather than silently
  capping (which desynced the venue allowance from swapData and could
  underflow the totals).
- V3ProviderOracle.peek floors a dust position's price to a non-zero unit
  instead of reverting, so a tiny position stays liquidatable (a reverting
  peek would block Moolah.liquidate and accrue bad debt); zero-leg-price
  still fails closed.

Counter-tests added for each.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
previewRemoveLiquidity (and its previewRedeemUnderlying wrapper) computed
spot principal + idle only, understating the withdrawal by uncollected
fees. Pro-rate positionAmountsAt(spot) instead — it simulates pending
fees + idle, matching removeLiquidity's actual spot-priced delivery.
Kept at spot (not fair) so the preview tracks the real burn for minAmount
sizing.

Counter-test accrues real swap fees and asserts the preview includes them.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The branch's IStakeManager (and its mock) add deposit() that new master also
declares — on the old base there was no clash, but rebased onto current master
the two collide into a duplicate function definition that fails to compile.
Remove the redundant declaration; a single deposit()/instantWithdraw() remains.

Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
@hashdit-bot

hashdit-bot Bot commented Jul 29, 2026

Copy link
Copy Markdown

Pull Request Review

This Solidity DeFi PR introduces a UUPS-based V3 collateral architecture comprising ERC-4626 provider vaults, concentrated-liquidity adapters, share-price oracles, and a dedicated liquidation contract for BSC and Ethereum LST/native pairs. It also adds rate-anchored valuation and rebalancing, native-token handling, loss and slippage controls, new dependencies, and extensive fork and regression tests.

Sensitive Content

No sensitive content detected.

Security Issues

🟠 [HIGH] Pre-existing native reserves can falsely satisfy flash-liquidation profitability

File: src/liquidator/V3Liquidator.sol
When the loan token is the wrapped-native asset, onMoolahLiquidate wraps the contract's entire native balance rather than only the native amount received during the current liquidation. Because the loan-token balance snapshot is taken before this conversion, pre-existing ETH/BNB reserves are counted as proceeds from the liquidation and can make an otherwise unprofitable operation pass out >= repaidAssets, allowing those reserves to be consumed for repayment.
Recommendation: Snapshot the native balance before redemption and wrap only the positive native balance delta attributable to the current liquidation. Add a regression test that pre-funds the liquidator with native currency, performs an unprofitable wrapped-native-loan liquidation, and verifies that it reverts without consuming the reserve.


Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

4 participants