feature: V3Provider - #151
Conversation
04e090b to
7d940c4
Compare
9ff43ab to
e1759f8
Compare
|
@audit-agent V3Liquidator.sol V3DexAdapter.sol V3Provider.sol SlisBNBV3DexAdapter.sol SlisBNBV3Provider.sol SlisBNBV3ProviderOracle.sol SlisBnbInventoryLib.sol V3PositionLib.sol |
🛡️ Cloud-Auditor — PR #151 reviewDispatched: 9 specialists · Head SHA: Tally
Top findingsH-01 — H-02 — M-01 — M-02 — Resilient-oracle TOKEN0/TOKEN1 legs must be rate-derived for the pool-manipulation invariant to hold M-03 — M-04 — M-05 — Lows (9)L-01 Missing Acknowledged / verified safe
Full report (PDF) delivered to the security Telegram group. |
Pull Request ReviewThis PR introduces a new V3 collateral-provider architecture for concentrated liquidity positions by adding multiple new Solidity contracts: a generic Sensitive ContentBlockchain Address:
Security Issues🟡 [MEDIUM] Input validation relaxed in
Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
Pull Request ReviewThis PR introduces a new V3 concentrated-liquidity provider architecture for slisBNB/WBNB by adding multiple new Solidity components: Sensitive ContentBlockchain Address:
Security IssuesNo serious security issues detected. Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
Pull Request ReviewThis PR introduces a major new Solidity web3 architecture for V3 concentrated-liquidity collateral: a split between Sensitive ContentBlockchain Address:
No sensitive key material or social account handles/links detected in non-test added files. Security IssuesNo serious security issues detected. Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
Pull Request ReviewThis PR introduces a new V3 concentrated-liquidity provider architecture for Moolah collateral by adding a large set of Solidity contracts: Sensitive ContentBlockchain Address:
Security Issues🟡 [MEDIUM] Input validation relaxed in
Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
Pull Request ReviewThis 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 ContentNo sensitive content detected. Security Issues🟠 [HIGH] Pre-existing native reserves can satisfy wrapped-native flash-liquidation repayment
Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
… 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>
_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>
Adding CLAMM liquidity is the one operation that must not be permissionless: a manipulated pool spot lets a caller force a bad-price re-add and sandwich it. Remove collectAndCompound from every permissionless user flow (deposit, withdraw, withdrawShares, supplyShares, redeemShares) and make compound() BOT-gated with a caller-supplied slippage floor (amount0Min/amount1Min forwarded to increaseLiquidity, previously hardcoded 0/0). Fee re-deployment now happens only via compound() or rebalance, both BOT-gated. No valuation regression: positionAmountsAt already simulates pending fees, so the health-check / oracle valuation stays fee-complete without an inline compound; removeLiquidity still collects the exiter's pro-rata fees. Counter-tests: compound is onlyBot; a deposit no longer deploys idle as liquidity. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…comments with BOT-gating Remove finding-ID / firm / process labels from shipped comments (no logic change): C-1, H02/Issue_04, M01, finding C/C4/D, Codex adv, AUDIT NOTE, "audit PR". Also correct two oracle @dev docstrings that still claimed peek reverts on zero total value (it now floors a dust position), and update the remaining compound comments that described the old permissionless model (compounding is now BOT-gated; user flows no longer compound). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Ceil-round amount0Used/amount1Used in the subsequent-deposit branch so the depositor pays >= their pro-rata share while shares still round down — every sub-wei rounding now favors existing holders and can never dilute the pool. Both legs stay <= the desired input (frac <= dᵢ·WAD/tᵢ), so no over-consumption. Also strip internal finding-id / auditor-name references from the V3 provider test comments (no behavior change; one test renamed accordingly). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
The rebase onto master auto-merged two copies of deposit() into IStakeManager (and its mock) at different line positions — no textual conflict, but a duplicate function definition that fails to compile. Remove the redundant declaration; the interface keeps a single deposit()/instantWithdraw(). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Mirror Liquidator.sol's fund-pool integration onto V3Liquidator: - fundSource (LiquidationVault) + reflowBlacklist storage, appended (UUPS-safe). - initialize takes a fundSource arg (0 = legacy); set directly (contract check only — vault registration necessarily happens after the proxy exists) + setFundSource for later. - setFundSource validates the vault has registered this liquidator; setReflowBlacklist. - withdrawERC20/ETH gate relaxed to MANAGER or fundSource so the vault's collect* pulls. - onMoolahLiquidate pulls the exact repayment shortfall from the vault (local balance first). - liquidate/flashLiquidate/redeemV3Shares reflow the residue (loanToken + redeemed legs + native) to the vault; the transfer-restricted V3 share collateral is never reflowed. - liquidate now routes a non-redeeming callback so the pool can fund it, and emits V3Liquidation. fundSource == 0 preserves the exact legacy pre-funded behavior. Counter-tests cover initialize+setFundSource validation, withdraw gate, vault-funded liquidate, and residue reflow. 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>
Pull Request ReviewThis 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 ContentNo sensitive content detected. Security Issues🟠 [HIGH] Pre-existing native reserves can falsely satisfy flash-liquidation profitability
Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
…wap leg
In onMoolahLiquidate the native (BNB/ETH) leg was sold with
call{ value: actualRedeemedAmount }, but the bot builds the 1inch swapData
off-chain against a pre-estimated input. Native swaps require msg.value to
equal the amount encoded in the calldata, so the variable actual amount
mismatches and the aggregator reverts — failing the whole flash liquidation.
Send the pre-agreed minTokenNAmt (the value the swapData was built for) as
msg.value instead; redeemShares guarantees actual >= min so the balance always
covers it, and the leftover stays to be wrapped/reflowed. Mirrors the
smart-collateral path in Liquidator.sol. The ERC-20 leg is unaffected (approve
is a ceiling). Counter-test uses a strict aggregator mock (msg.value == amountIn);
V3LiquidatorEth test updated to set minToken1Amt to the native swap input.
Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
decreaseLiquidity settles the whole position's accrued fees into tokensOwed, so collecting them together with the pro-rata burned principal paid a partial withdrawer 100% of the fees — diluting the remaining holders. Collect the fees into idle first (collect only claims owed tokens: no swap, no spot move, safe on the user path), then burn the pro-rata principal and split idle+fees pro-rata. Full redeems are unchanged (pro-rata = 100%). rebalance / _collectAndCompound already keep collected fees in the vault for all holders, so they are unaffected. Counter-test: with real accrued swap fees, a partial withdraw no longer drops the remaining holders' per-share value. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…eposit-withdraw cycle A subsequent deposit's consumed amounts stay pinned to the fair composition, but shares are now min(sharesFair, sharesSpot): the spot quote re-prices the same consumed amounts against the pool-spot composition. Withdraw settles at spot, so crediting only on fair let fair-valued shares be redeemed against a richer spot composition (the deposit-withdraw cycle leak) — the min caps the credit at what a spot exit can back. Manipulation-resistant (spot can only lower the credit, never above fair); minShares is the depositor's MEV floor. Add previewDepositShares mirroring the exact credit (shared _quoteDeposit helper so preview can't drift from the mint) so frontends size minShares correctly. NatSpec + readable names on the new/changed functions and the IV3Provider surface. Counter-tests: skewed-spot credits fewer shares, cycle no longer profitable, minShares backstops a spot squeeze, and previewDepositShares == actual mint. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
A2 min(fair,spot) deposit crediting caps a large imbalanced deposit at its spot-exit value, so surplus accrues to holders and the settled peek sits above the pre-deposit price. Replace the stale "peek == normal price" check with the true invariants: peekDuring == peekAfter (no transient inflation) and peekDuring >= peekNormal (deposits only raise peek). The reentrancy attack stays fully neutralized (attacker solvent, no bad debt). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
WstETHV3Provider and WbETHV3Provider inherit V3Provider.deposit unchanged, so the min(fair,spot) credit closes the deposit-withdraw cycle on the ETH pairs too. Add cycle tests to both: a skewed spot credits fewer shares, preview == mint at a skewed spot, and the deposit->withdraw cycle extracts no value. wstETH uses the deep live Uniswap V3 pool. The only wbETH/WETH pool is empty, so the first deposit bootstraps it with our own liquidity under pure-rate mode and a wide center band; the guard relaxation is scoped to the cycle tests so the existing wiring assertions keep their defaults. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
fix(provider): credit min(fair,spot) shares on deposit to close the deposit-withdraw cycle
rebalance takes an expectedCenterRate the BOT reads via the new adapter centerRate() view and passes back; if the live LST↔native rate deviates from it by more than maxCenterRateDeviationBps the call reverts. This bounds the range anchor against a rate anomaly between build and execution — invisible to the fair-NAV loss caps, which measure in the same rate frame. Both sides opt in (expectedCenterRate != 0 per-call, maxCenterRateDeviationBps != 0 globally); 0 preserves prior behavior. Adds the MANAGER setter, the centerRate() getter, and doc-faithful counter-tests (real slippage/target floors, not zeros). Storage is append-only (upgrade-safe). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
v0.1.1 false-positived V3DexAdapter's __gap consumption (it read the __gap array shrink 45->44 as a type change at the gap slot). v0.1.2 is __gap-aware: the standard pattern — shrink __gap and place the new variable in the freed slot — validates as UPGRADE SAFE, so the committed maxCenterRateDeviationBps layout passes without contorting the storage. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…guard feat(provider): assert expectedCenterRate on rebalance
sellBNB already measures actualIn from the balance delta and validates it, but emitted the requested amountIn, so a venue that refunds part of the forwarded value was logged as if the full amount was sold. That gave SellToken different semantics per path: the ERC20 _sellToken emits the consumed amount, the native path emitted the requested one. No fund-flow impact — ExceedAmount and NoProfit already use the measured values — but off-chain accounting read the wrong input. Counter-test drives a venue that consumes 0.6 of 1.0 BNB and refunds the rest; it fails against the previous code (logs 1.0 instead of 0.6). Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
…ng, range params Liquidator: - onMoolahLiquidate counts native in the before-snapshot when the loan token is the wrapped-native, so stray native from earlier redemptions or receive() donations no longer reads as fresh profit and lets a shortfall clear NoProfit. - redeemV3Shares requires receiver == self when a fundSource is set, keeping vault-funded proceeds inside the reflow path. - setReflowBlacklist rejects a no-op status change, matching the other setters. Provider: - previewDepositAmounts rounds the leg amounts UP, matching _quoteDeposit, so the preview reports exactly what deposit() consumes. - Range half-width INITIAL_RANGE_BPS 100 -> 50 (+/-0.5%), which also tightens the maxSpotDeviationBps and maxTwapDeviationBps defaults; centerRateThresholdBps drops to 1bp so the BOT, not the contract, picks the rebalance cadence. - Correct two stale comments: the zero-liquidity guard covers compound only (the rebalance re-mint has none), and the spot-vs-fair gate does not apply to the rebalance re-mint. Drop the previewDepositForToken0 suggestion to deposit a single leg when fair leaves the range -- every shape reverts until a recenter. Tests: - Counter-tests for each liquidator fix, incl. a WBNB-loan market so the wrapped-native branch is exercised. - previewDepositAmounts exact-match test; regression test pinning that deposits are closed while fair sits past tickUpper and reopen after a BOT recenter. - _deposit helpers derive a non-zero minShares from previewDepositShares: min0/min1 floor the consumed amounts, not the entry price. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Bailsec V3 Collateral final report (26 Jul, 75pp), HashDit V3 LP Collateral (29 Jun - 14 Jul, 102pp) plus its follow-up update (24 - 28 Jul, 16pp), and CertiK's preliminary comments on V3Provider (assessed 8 Jul, 93pp), filed under the existing docs/audits naming convention. Co-Authored-By: Claude Opus 4.8 <noreply@anthropic.com>
Audit/v3 lp fixes
Pull Request ReviewThis Solidity DeFi PR introduces an upgradeable V3 concentrated-liquidity collateral system split across vault, DEX adapter, oracle, and liquidator contracts, with implementations for slisBNB/WBNB, wstETH/WETH, and wbETH/WETH. It also adds manipulation-resistant valuation, guarded rebalancing and liquidation flows, native-token handling, extensive fork/regression tests, audit artifacts, and CI/dependency updates. Sensitive ContentNo sensitive content detected. Security IssuesNo serious security issues detected. Generated by Hashdit Bot. This tool can absolutely NOT replace manual audits. |
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:
market.oracle. Prices the share off the adapter's manipulation-resistant fair composition + the resilient oracle, with a capped haircut.Architecture (3-contract split)
V3Provider(vault)V3DexAdapter(strategy)SlisBNBV3ProviderOracle(oracle)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
NotSlisBnbWbnbPair); the oracle additionally asserts its tokens match the adapter's (AdapterPairMismatch), so a mis-wired deploy can't silently misprice.convertSnBnbToBnb), not pool spot/TWAP —peek/totalAssets/getUserBalanceInBnbare invariant to pool-price manipulation. Per-leg USD comes from the resilient oracle;peekreverts on a zero leg price or zero total value (finding D);supply == 0returns 0(pre-market).
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.deposit/instantWithdraw, measured by balance delta — not pool-manipulable), guarded by a configurable rate-drift threshold (centerRateThresholdBps) +deadline+minLiquidity. BOT-gated on the vault;onlyProvideron the adapter._mint+supplyCollateral(CEI), so the native-BNB refund callback can't observe an inflated share price (staletotalSupplyvs. already-added NAV) and reenterMoolah.borrow. Withdraw/redeem likewise burn before the adapter pushesunderlying to the receiver.
idleToken0/idleToken1storage (notbalanceOf) so donations can't inflate NAV.msg.value(auto-wrapped to WBNB); withdrawals unwrap WBNB to native BNB.AccessControlEnumerableUpgradeableacross vault/adapter/oracle/liquidator (codebase standard; enumerable roles).IV3PoolMinimalslot0 read — decodes onlysqrtPriceX96/tick, width-agnostic tofeeProtocol(Uniswapuint8vs PancakeSwapuint32), so the adapter works against any V3 fork.lista-dao-contractslibs; interfaces from thelista-v3submodule.Files
src/provider/V3Provider.solsrc/provider/SlisBNBV3Provider.solsrc/provider/V3DexAdapter.solsrc/provider/SlisBNBV3DexAdapter.solsrc/provider/SlisBNBV3ProviderOracle.solmarket.oracle— share pricing off fair view + haircutsrc/provider/interfaces/*.solIV3Provider,IV3DexAdapter,ISlisBNBV3DexAdapter,IV3ProviderOracle,IV3PoolMinimal,IStakeManagersrc/provider/libraries/V3PositionLib.solsrc/provider/libraries/SlisBnbInventoryLib.solsrc/liquidator/V3Liquidator.soltest/provider/SlisBNBV3Provider.t.soltest/provider/SlisBNBV3ProviderRate.t.soltest/provider/V3ProviderReentrancyPoC.t.soltest/liquidator/V3Liquidator.t.solTest Plan
All suites fork BSC mainnet at block 60541406 (slisBNB/WBNB 1bp pool; the live StakeManager is etched with a faithful
instantWithdrawstand-in for the rebalance conversion).SlisBNBV3Provider.t.sol) — deposit/withdraw/redeem/supplyShares/withdrawShares, value-based share math, previews, oraclepeek/getTokenConfig+ finding-D, one-sided & slippage guards, transfer restrictions, slisBNBx integration, rebalance (incl. out-of-range via inventory conversion), borrow/repay/liquidate.SlisBNBV3ProviderRate.t.sol) —peek/totalAssets/getUserBalanceInBnbinvariant to pool manipulation; rate-centered rebalance + drift/deadline/minLiquidity guards. (8 tests)V3Liquidator.t.sol) — pre-funded + flash liquidation, share redemption, token/BNB swaps, whitelist/access control. (27 tests)V3ProviderReentrancyPoC.t.sol) —test_C1_depositRefundReentrancy_neutralizeddrives the full attack; fails pre-fix (≈26× inflation, insolvent), passes post-fix.