Skip to content
Quantix Security Labs

Risk summary

State at remediation commit a91c7e2d04b3

Findings
10
Critical and high
3
Open or partial
1
Remediation
95%

Findings by severity

  • Critical1
  • High2
  • Medium3
  • Low2
  • Informational2

Remediation statusAnalysis

  • Resolved8
  • Partially resolved1
  • Acknowledged1
  • Open0

Quillon Vaults

Yield

Document
QX-TSA-2026-001 · v1.1
Type
Smart Contract Audit
Review
27 Jul 2026 – 7 Aug 2026
Published
18 Aug 2026
Ecosystems
Ethereum
Languages
Solidity
Risk Index
A82.5
Assessment contents · 12 sections

01

Assessment overview

Quillon Vaults lets depositors pool an ERC-20 asset into a vault that allocates capital across yield strategies. The review focused on share accounting, strategy valuation and the privileged paths that can move funds or change code.

Ten findings were reported: one critical, two high, three medium, two low and two informational. The most significant allowed the first depositor to inflate the share price and capture subsequent deposits; another let an attacker inflate strategy valuation within a single transaction using the pool spot price.

All critical, high and medium findings were resolved in the remediation commit and verified, including new invariant tests for all four ERC-4626 conversion paths. One low finding was acknowledged with a documented plan and one was partially resolved. Residual risk is concentrated in strategies added after this review, which were out of scope.

02

Technical analysis: protocol architecture

Users deposit the underlying asset into QuillonVault and receive shares. The vault keeps a liquidity buffer and allocates the remainder through StrategyManager to one or more strategies. Total assets reported by the vault are the buffer plus each strategy’s self-reported estimate.

AmmLpStrategy provides liquidity to a constant-product pool and values its LP position from pool reserves. Harvests swap reward tokens back to the underlying asset and route a performance fee to a fee recipient.

When the buffer cannot cover a withdrawal, requests enter WithdrawalQueue and are claimed after the strategy unwinds. All parameter changes and implementation upgrades pass through QuillonTimelock; the vault is a UUPS proxy whose upgrade function is restricted to the timelock.

Trusted· 4

Assumed to behave as specified; compromise is out of the threat model.

  • QuillonVault (UUPS proxy)Share accounting, deposits, withdrawals, buffer management
  • StrategyManagerCapital allocation, harvests, fee routing
  • WithdrawalQueueDeferred withdrawals from illiquid strategies
  • QuillonTimelockDelays upgrades and parameter changes
trust boundary

Semi-trusted· 2

Trusted for liveness or within bounds; misbehaviour is partly modelled.

  • AmmLpStrategyLP position management and valuation
  • KeeperTriggers harvests and rebalances
trust boundary

Untrusted· 1

May act adversarially; every input is validated.

  • External AMM poolLiquidity venue and price source for valuation
Components grouped by trust level. Calls that cross a trust boundary are where the review concentrated input validation and authorisation checks.

Trust assumptions

  • The timelock is controlled by a multisig whose signers act honestly; users have the timelock delay to exit before changes take effect.
  • Strategies report their assets honestly within the bounds of their valuation method; a strategy is not assumed to be adversarial, but its price inputs are.
  • The underlying asset is a standard ERC-20 without transfer fees or rebasing.
  • Keepers may be delayed or offline but cannot move funds outside configured strategies.

03

Scope

Vault core, strategy manager, LP strategy, withdrawal queue and timelock. 1,840 nSLOC of Solidity.

Repository
github.com/example-org/quillon-vaults
Review effort
Two reviewers, ten working days, plus a three-day remediation review
Reviewed commit
4be1f0c9d2a7e35b18c64f0e9a2d7713c5b80e41
Remediation commit
a91c7e2d04b36f58e1d9c2b740f6a3e85d1c09bb
Files and components in scope
PathDescriptionnSLOC
src/QuillonVault.solERC-4626 vault: deposits, withdrawals and share accounting412
src/StrategyManager.solStrategy allocation, harvest and fee routing386
src/strategies/AmmLpStrategy.solLiquidity-provision strategy and position valuation344
src/WithdrawalQueue.solQueued withdrawals for illiquid strategies268
src/base/VaultBase.solShared upgradeable base: roles, pausing, storage182
src/governance/QuillonTimelock.solTimelocked parameter changes and upgrades248
Total6 items1,840

Out of scope

  • Strategies other than AmmLpStrategy, including those added after the review commit
  • The external AMM and lending protocols the strategies integrate with
  • Off-chain keepers that trigger harvests and rebalances
  • Front-end application and deployment scripts

04

Threat model

Each threat lists the actor, the vector and the mitigation, and links to the findings that realised it in the reviewed code.

Assets at risk

  • Depositor principal held by the vault and strategies
  • Accrued yield and performance fees
  • Implementation code and upgrade authority
  • Share price integrity used by integrators

Actors

  • DepositorUntrustedDeposits, withdraws, transfers shares
  • Flash-loan attackerUntrustedMoves pool reserves within one transaction
  • KeeperSemi-trustedCalls harvest and rebalance at chosen times
  • Governance multisigTrustedQueues upgrades and parameter changes through the timelock
Threats, vectors, mitigations and related findings
ThreatActorVectorMitigationFindings
Share price manipulation on an empty vaultDepositorDonation to the vault before others depositVirtual shares offset and zero-share revertQV-01
Strategy valuation inflated within one transactionFlash-loan attackerMoving AMM reserves read by the strategyTWAP valuation with spot deviation guardQV-02
Fee redirectionDepositorUnprotected setterGovernance-only modifierQV-03
Double claim from the withdrawal queueDepositorRe-entering claim through token hooksChecks-effects-interactions and reentrancy guardQV-04
Value leakage through roundingDepositorRepeated small withdrawalsRounding in the vault’s favour on all pathsQV-05
Takeover or bricking of the implementationDepositorInitialising the implementation contract directlyDisable initializers in the constructorQV-06
Upgrades without user noticeGovernance multisigTimelock delay reduced to zeroMinimum delay enforced on-chainQV-08

05

Findings

Severity

10 findings

QV-01CriticalResolvedFirst-depositor attack

First depositor can inflate share price and capture subsequent deposits

src/QuillonVault.sol:L188–L214

Likelihood
High
Impact
High
Weakness
CWE-682
Description

Share minting uses assets * totalSupply / totalAssets(), rounding down, with no virtual offset. When the vault is empty, the exchange rate is defined entirely by whatever the first depositor does.

An attacker deposits 1 wei and receives one share. They then transfer a large amount of the asset directly to the vault without calling deposit. totalAssets() now includes the donation while totalSupply is still 1, so a single share is worth the entire balance.

Any later deposit smaller than the donation computes assets * 1 / (donation + 1), which rounds down to zero shares. The deposit is accepted, the depositor receives nothing, and the assets accrue to the attacker’s single share.

Preconditions
  • The vault has zero or near-zero total supply (new deployment or fully drained vault)
  • The attacker can front-run or precede the first legitimate deposit
  • The vault does not revert on zero-share mints
Exploit scenario
  1. 1.Attacker observes a pending first deposit of 100,000 USDC in the mempool.
  2. 2.Attacker front-runs it: deposit(1 wei) mints 1 share.
  3. 3.Attacker transfers 100,001 USDC directly to the vault, making totalAssets = 100,001 USDC + 1 wei.
  4. 4.The victim’s deposit computes 100,000e6 × 1 / 100,001e6 = 0 shares and succeeds.
  5. 5.Attacker redeems 1 share for ~200,001 USDC, netting the victim’s deposit minus gas.
Impact

Total loss of any deposit made while the attacker controls the inflated share. Losses are bounded only by the size of victim deposits, and the pattern can be repeated whenever the vault returns to an empty state.

Recommendation

Introduce a virtual share and asset offset in the conversion functions (for example a decimals offset as in OpenZeppelin’s ERC4626 implementation), or mint dead shares to an unrecoverable address at initialisation. In addition, revert any deposit that would mint zero shares and any mint that would require zero assets.

Fix
src/QuillonVault.soldiff
   function _convertToShares(uint256 assets, Math.Rounding r) internal view returns (uint256) {-      uint256 supply = totalSupply();-      return supply == 0 ? assets : assets.mulDiv(supply, totalAssets(), r);+      return assets.mulDiv(totalSupply() + 10 ** _decimalsOffset(), totalAssets() + 1, r);   }    function deposit(uint256 assets, address receiver) public override returns (uint256 shares) {       shares = previewDeposit(assets);+      if (shares == 0) revert ZeroShares();       _deposit(msg.sender, receiver, assets, shares);   }
Resolution

Fixed with a decimals offset of 6 and a zero-share revert on deposit and mint. Verified in the remediation commit; INV-03 added to the invariant suite.

QV-02HighResolvedOracle manipulation

Strategy valuation uses manipulable AMM spot price

src/strategies/AmmLpStrategy.sol:L97–L131

Likelihood
Medium
Impact
High
Weakness
CWE-807
Description

estimatedTotalAssets() values the LP position by reading the pool’s current reserves and computing the position’s share of each side at the spot ratio. The vault includes this estimate in totalAssets(), which drives both minting and redemption.

Reserves can be moved arbitrarily within one transaction using borrowed capital. Because the valuation reads reserves at the moment of the call, an attacker can skew the ratio, interact with the vault at the distorted price, and restore the pool before the transaction ends.

The LP token itself is not priced naively (the strategy decomposes it into underlying amounts), but both amounts are derived from the same manipulable reserves, so the decomposition does not help.

Preconditions
  • Flash-loan liquidity in the pooled assets is available
  • The vault allows deposit and redeem in the same transaction
  • The strategy holds a material share of vault assets
Exploit scenario
  1. 1.Attacker flash-borrows a large amount of token B.
  2. 2.Attacker swaps B for A in the pool, skewing reserves so that the strategy’s LP position is valued above its fair value.
  3. 3.Attacker redeems shares from the vault at the inflated totalAssets(), receiving more underlying than their shares are worth.
  4. 4.Attacker swaps back to restore the pool and repays the flash loan.
  5. 5.The loss is borne by remaining depositors through a lower share price.
Impact

Value extraction from depositors in a single transaction, repeatable while the strategy is active. Profitability depends on pool depth and the strategy’s share of vault assets; at the reviewed configuration the strategy held about 60% of assets.

Recommendation

Value positions with a manipulation-resistant reference price: a time-weighted average price over a window long enough that sustaining a distortion is uneconomic, or an external oracle with deviation checks. Additionally compare spot to the reference and revert deposits and withdrawals when the deviation exceeds a bound.

Fix
src/strategies/AmmLpStrategy.soldiff
   function estimatedTotalAssets() public view returns (uint256) {-      (uint112 r0, uint112 r1, ) = pool.getReserves();-      return _lpValue(uint256(r1) * 1e18 / r0) + idle();+      uint256 twap = oracle.consult(address(asset), TWAP_WINDOW); // 30 minutes+      uint256 spot = _spotPrice();+      if (_deviationBps(spot, twap) > MAX_DEVIATION_BPS) revert PriceDeviation(spot, twap);+      return _lpValue(twap) + idle();   }
Resolution

Valuation now uses a 30-minute TWAP with a 2% spot deviation guard applied to deposits and withdrawals. INV-02 holds under fuzzing with flash-loan sized reserve changes.

QV-03HighResolvedAccess control

Fee recipient can be changed by any caller

src/StrategyManager.sol:L64

Likelihood
High
Impact
Medium
Weakness
CWE-862
Description

setFeeRecipient(address) is declared external without the onlyGovernance modifier applied to every other setter in the contract. The function writes the address that receives performance and management fees on each harvest.

The omission appears to have been introduced when the function was moved from the vault into StrategyManager: the vault version was restricted, the moved version was not. No test covered calling the setter from an unprivileged account.

Preconditions
  • None beyond knowing the function exists
Exploit scenario
  1. 1.Attacker calls setFeeRecipient(attacker) from any account.
  2. 2.The next keeper harvest transfers the performance fee to the attacker.
  3. 3.Fees continue to flow to the attacker until governance notices and resets the recipient through the timelock.
Impact

Loss of all protocol fees from the moment of the change until it is reverted. Depositor principal is not directly affected, but the timelock delay means the protocol cannot react quickly.

Recommendation

Restrict the function to the timelocked governance role, emit an event on change, and add a negative test for every privileged function.

Fix
src/StrategyManager.soldiff
-  function setFeeRecipient(address recipient) external {+  function setFeeRecipient(address recipient) external onlyGovernance {+      if (recipient == address(0)) revert ZeroAddress();+      emit FeeRecipientUpdated(feeRecipient, recipient);       feeRecipient = recipient;   }
Resolution

Modifier added; FeeRecipientUpdated emitted; negative tests added for all governance setters.

QV-04MediumResolvedReentrancy

Withdrawal queue updates state after external token transfer

src/WithdrawalQueue.sol:L142–L160

Likelihood
Low
Impact
High
Weakness
CWE-841
Description

claim(requestId) checks that the request is ready, transfers the assets to the owner, and only then sets requests[requestId].claimed = true. The function has no reentrancy guard.

For a standard ERC-20 the transfer does not call back into the recipient, so the ordering is harmless. If the vault is deployed with an asset that invokes hooks on transfer (ERC-777 style or tokens with receive callbacks), the recipient regains control before the claimed flag is written and can call claim again for the same request.

Preconditions
  • The vault’s underlying asset invokes a recipient hook during transfer
  • The attacker owns a ready withdrawal request
Exploit scenario
  1. 1.Attacker queues a withdrawal and waits until it is claimable.
  2. 2.Attacker calls claim(id) from a contract that implements the token’s receive hook.
  3. 3.Inside the hook, the contract calls claim(id) again; the request is still unclaimed, so it pays out a second time.
  4. 4.The recursion repeats until the queue’s balance is exhausted or gas runs out.
Impact

Multiple payouts for one request, draining assets reserved for other queued withdrawals. Standard ERC-20 deployments are not affected, which keeps likelihood low, but the vault is designed to support arbitrary assets.

Recommendation

Follow checks-effects-interactions: mark the request claimed before transferring, and add a reentrancy guard to claim and claimMany.

Fix
src/WithdrawalQueue.soldiff
-  function claim(uint256 id) external {+  function claim(uint256 id) external nonReentrant {       Request storage r = requests[id];       if (r.claimed || block.timestamp < r.readyAt) revert NotClaimable(id);-      asset.safeTransfer(r.owner, r.assets);-      r.claimed = true;+      r.claimed = true;+      asset.safeTransfer(r.owner, r.assets);   }
Resolution

State update moved before the transfer; nonReentrant added to claim and claimMany. INV-04 holds under fuzzing with a hook-enabled test token.

QV-05MediumResolvedPrecision and rounding

previewWithdraw rounds in the user’s favour

src/QuillonVault.sol:L240

Likelihood
Medium
Impact
Medium
Weakness
CWE-682
Description

EIP-4626 requires that rounding always favours the vault: previewWithdraw and previewMint must round up, previewDeposit and previewRedeem must round down. The implementation rounds down in previewWithdraw, so a withdrawal of a given asset amount burns fewer shares than it should.

The difference is at most one share unit per call, but it is systematic and in one direction. An account that repeatedly withdraws small amounts extracts slightly more value than its shares represent.

Preconditions
  • Gas cost per withdrawal is lower than the rounding gain, which is plausible on low-fee networks or for high-value share units
Exploit scenario
  1. 1.Attacker holds shares and calls withdraw with an asset amount chosen so the exact share cost has a large fractional part.
  2. 2.The vault burns the rounded-down share count.
  3. 3.Repeating many times accumulates the fractional difference as extra assets.
Impact

Gradual value leakage from remaining depositors. Negligible per call at current gas prices, but it violates the standard integrators rely on.

Recommendation

Use Math.mulDiv with Rounding.Ceil for share calculations on withdraw and mint paths, and add invariant tests for all four conversion paths.

Fix
src/QuillonVault.soldiff
   function previewWithdraw(uint256 assets) public view returns (uint256) {-      return _convertToShares(assets, Math.Rounding.Floor);+      return _convertToShares(assets, Math.Rounding.Ceil);   }
Resolution

Rounding direction corrected; INV-01 added for all four conversion paths.

QV-06MediumResolvedUnsafe proxy upgrade

Implementation contract can be initialised by anyone

src/QuillonVault.sol:L72

Likelihood
Medium
Impact
Medium
Weakness
CWE-665
Description

The vault implementation behind the UUPS proxy does not call _disableInitializers() in its constructor. The proxy was initialised correctly at deployment, but the implementation contract itself still has uninitialised storage and a callable initialize.

An attacker can call initialize on the implementation address and become its owner and upgrader. In UUPS, the upgrade logic lives in the implementation, so the attacker can then call upgradeToAndCall on the implementation itself and execute arbitrary code in its context.

Preconditions
  • The implementation is deployed without disabled initializers
Exploit scenario
  1. 1.Attacker reads the implementation address from the EIP-1967 slot of the proxy.
  2. 2.Attacker calls initialize on the implementation, setting themselves as owner.
  3. 3.Attacker calls upgradeToAndCall on the implementation with a contract that self-destructs or otherwise corrupts it.
Impact

Proxy storage is unaffected, but corrupting or destroying the implementation can make the proxy unusable until governance deploys and upgrades to a new implementation. Since EIP-6780 limits SELFDESTRUCT, the practical impact today is lower, which is why the finding is rated medium.

Recommendation

Call _disableInitializers() in the implementation constructor and include an initializer test in the deployment script.

Fix
src/QuillonVault.soldiff
+  /// @custom:oz-upgrades-unsafe-allow constructor+  constructor() {+      _disableInitializers();+  }
Resolution

Constructor added; deployment script asserts the implementation reports initialised.

QV-07LowAcknowledgedUnsafe proxy upgrade

Upgradeable base contracts lack storage gaps

src/base/VaultBase.sol

Likelihood
Low
Impact
Medium
Description

VaultBase declares state variables and is inherited by QuillonVault, which declares its own variables afterwards. VaultBase reserves no storage gap and does not use namespaced storage.

If a future upgrade adds a variable to VaultBase, every variable in QuillonVault shifts by one slot and reads values that belonged to a different variable. Storage-layout tooling would catch this only if it is run on every upgrade.

Preconditions
  • A future upgrade adds state to an inherited base contract
Exploit scenario
  1. 1.Governance upgrades to a version that adds a variable to VaultBase.
  2. 2.Child-contract variables now read shifted storage, corrupting accounting.
Impact

No current impact. Raises the risk of storage collisions in future upgrades, which could corrupt accounting irrecoverably.

Recommendation

Reserve a __gap array in each upgradeable base, or migrate to ERC-7201 namespaced storage before the next upgrade, and run a storage-layout diff in CI.

Resolution

Acknowledged. The team will migrate to ERC-7201 namespaced storage in the next major version and has added a storage-layout check to CI.

QV-08LowPartially resolvedGovernance manipulation

Timelock minimum delay can be set to zero

src/governance/QuillonTimelock.sol:L51

Likelihood
Low
Impact
Medium
Weakness
CWE-284
Description

updateDelay(uint256) accepts any value, including zero. The function is itself timelocked, so a reduction takes one full delay to apply, but once applied every later change, including implementation upgrades, executes immediately.

The timelock is the only control that gives depositors time to exit before a harmful upgrade. Allowing it to reach zero removes that guarantee for the remainder of the protocol’s life.

Preconditions
  • Governance keys are compromised or act carelessly
Exploit scenario
  1. 1.A compromised multisig queues updateDelay(0); users who are not monitoring the queue do not notice.
  2. 2.After the delay, the change executes; subsequent upgrades are immediate.
  3. 3.The attacker upgrades the vault to an implementation that transfers all assets.
Impact

Removes users’ ability to react to governance actions. Combined with key compromise, enables an immediate drain.

Recommendation

Enforce a minimum delay in the contract (we recommended 48 hours) and alert on any queued delay change.

Fix
src/governance/QuillonTimelock.soldiff
   function updateDelay(uint256 newDelay) external onlySelf {+      if (newDelay < MIN_DELAY) revert DelayTooShort(newDelay, MIN_DELAY); // MIN_DELAY = 24 hours       emit DelayUpdated(delay, newDelay);       delay = newDelay;   }
Resolution

A 24-hour floor was added. The team considers 24 hours sufficient for its user base; our recommendation of 48 hours stands and the residual risk is documented.

QV-09InformationalResolvedCode quality

Parameter changes do not emit events

src/StrategyManager.sol (multiple)

Likelihood
Low
Impact
Low
Weakness
CWE-778
Description

Several setters change risk parameters (strategy debt ratios, deviation bounds, keeper address) without emitting events. Off-chain monitoring has to diff storage to notice changes.

Preconditions
  • Not applicable
Exploit scenario
  1. 1.A parameter is changed; monitoring that relies on events does not detect it.
Impact

No direct impact. Reduces the effectiveness of monitoring and incident response.

Recommendation

Emit an event with old and new values for every privileged state change.

Resolution

Events added for all setters.

QV-10InformationalResolvedCode quality

Floating pragma and unused imports

src/* (multiple)

Likelihood
Low
Impact
Low
Weakness
CWE-664
Description

Contracts declare pragma solidity ^0.8.20 and import libraries that are no longer used. The tested compiler version may differ from the deployed one.

Preconditions
  • Not applicable
Exploit scenario
  1. 1.A newer compiler with different optimiser behaviour is used at deployment than in testing.
Impact

No direct impact. Compiler drift between testing and deployment.

Recommendation

Pin the compiler version and remove unused imports.

Resolution

Pinned to 0.8.24; unused imports removed.

06

Severity

10findings
  • Critical1 · 10%
  • High2 · 20%
  • Medium3 · 30%
  • Low2 · 20%
  • Informational2 · 20%

By vulnerability class

  • Code quality2
  • Unsafe proxy upgrade2
  • Access control1
  • First-depositor attack1
  • Governance manipulation1
  • Oracle manipulation1
  • Precision and rounding1
  • Reentrancy1

Likelihood and impact

Each finding placed by the likelihood of its preconditions and the impact of exploitation.

Findings by likelihood (columns) and impact (rows)
Impact ↓ / Likelihood →LowMediumHigh
High
Medium
Low
0
0

07

Remediation

8 of 10 findings are resolved, 1 acknowledged, 1 partially resolved and 0 open at remediation commit a91c7e2d04b3.

Remediation progress95%

Resolved and acknowledged findings count fully; partially resolved findings count half.

95%remediated
  • Resolved8 · 80%
  • Partially resolved1 · 10%
  • Acknowledged1 · 10%
  • Open0 · 0%
Findings by severity and remediation status
SeverityResolvedPartially resolvedAcknowledgedOpenTotal
Critical10001
High20002
Medium30003
Low01102
Informational20002
All findings811010

Residual items

  • QV-07LowAcknowledgedAcknowledged. The team will migrate to ERC-7201 namespaced storage in the next major version and has added a storage-layout check to CI.
  • QV-08LowPartially resolvedA 24-hour floor was added. The team considers 24 hours sufficient for its user base; our recommendation of 48 hours stands and the residual risk is documented.
  • ResolvedFixed and verified in the remediation review.
  • Partially resolvedMitigated; the residual risk is documented.
  • AcknowledgedAccepted by the protocol team with a stated rationale.
  • OpenNot yet addressed at the reviewed remediation commit.

08

Formal verification

Formal verification was not part of this engagement. Four accounting properties were instead encoded as invariants and checked by fuzzing for 50 million calls against the remediation commit. They are listed for traceability; fuzzing results are evidence, not proofs.

Tool: Invariant fuzzing (Foundry)

  • INV-01InconclusiveconvertToAssets(convertToShares(x)) ≤ x for all xNo violation in 50M calls after the QV-05 fix; not formally proved.
  • INV-02InconclusivetotalAssets() ≥ sum of assets redeemable by all holdersHeld under fuzzing with TWAP valuation; violated on the original commit (QV-02).
  • INV-03InconclusiveA deposit that mints zero shares revertsIntroduced with the QV-01 fix; held under fuzzing.
  • INV-04InconclusiveEach withdrawal request is paid at most onceViolated on the original commit with hook-enabled tokens (QV-04); held after the fix.

A proved property holds for every execution of the model under its stated assumptions. Fuzzing and property-based results are evidence, not proofs, and are recorded as inconclusive.

09

Recommendations

  1. 01

    Assess every new strategy before activation

    Strategies define most of the vault’s risk. Treat each new strategy as a separate review scope with its own valuation analysis, and require a timelocked activation so depositors can exit.

  2. 02

    Keep the invariant suite in CI

    The four invariants written during this engagement caught three of the findings. Run them on every pull request and extend them when new conversion paths or strategies are added.

  3. 03

    Monitor price deviation and timelock queues

    Alert when the TWAP deviation guard trips repeatedly, when any delay change is queued, and when an upgrade is queued. These are the earliest signals of the attack paths documented here.

  4. 04

    Raise the timelock floor

    Increase the minimum delay to 48 hours as originally recommended, and publish the delay so integrators can plan around it.

  5. 05

    Restrict supported assets

    Document that the vault supports standard ERC-20 tokens only, or keep the reentrancy guard on every path that transfers assets.

10

Methodology and limitations

  1. 01Line-by-line manual review of all in-scope contracts by two reviewers, each covering the vault and strategy independently
  2. 02Static analysis with manual triage of every detector result
  3. 03Invariant fuzzing of share accounting: total assets ≥ redeemable value, and conversion round-trips never create value
  4. 04Economic review of strategy valuation under single-block price manipulation, with flash-loan sized reserve changes
  5. 05Upgrade-path review: initializer handling, storage layout and timelock authorisation
  6. 06Remediation review of every finding against the fix commit, with re-run of all proofs of concept

Severity classification

Critical
Direct loss of funds or permanent loss of control is achievable with little or no precondition.
High
Material loss or protocol disruption is achievable under realistic conditions.
Medium
Limited loss, degraded functionality, or impact that depends on specific conditions.
Low
Minor impact, defence-in-depth gaps, or issues with unlikely preconditions.
Informational
Code quality, documentation or best-practice observations without direct security impact.

Limitations

  • Only the files and commit listed in scope were reviewed. Strategies added later are not covered.
  • External protocols integrated by the strategy were treated as behaving as documented.
  • The review does not cover off-chain keepers, front ends or deployment infrastructure.
  • Economic analysis assumed pool liquidity at the time of review; materially shallower pools change the cost of manipulation.

No assessment can show that a system is free of vulnerabilities. This research assessment reflects the code at the commits listed. Quantix methodology

11

Technical appendix

Tools

  • FoundryUnit, fork and invariant tests; proofs of concept
  • Static analyser with custom detectorsDetector sweep with manual triage
  • Storage layout diffUpgrade-safety comparison between commits
  • Manual reviewPrimary method for all findings
Reviewed files (SHA-256) · 6 files
SHA-256 hashes of reviewed files
FileSHA-256
src/QuillonVault.sol055aef8bbd18134315fadc2ccbfb94f2aff3ac33394c8320391f58782975680b
src/StrategyManager.sol9c95775339b455502cde3e94750c250c0a9d2c938477c3751c88e23e64656f5d
src/strategies/AmmLpStrategy.sol42340ac94bf877813bea4e163ed9c73c67c6d9b36ef6559ae19e0f1e718dd47d
src/WithdrawalQueue.sol71b171b444951d8b58778af20211d42ca304bde5b8faac7d404fdfa8e584913c
src/base/VaultBase.solacaeac0e97572e9efbf349faba3e53e4889d54ee7328f73015f8130a23cb22d7
src/governance/QuillonTimelock.sole840bba7545f1f1f3bcafec369d8cc001ccbbd900afcaee6b6d4b2d980c72aa9

Notes

  • Hashes are SHA-256 digests of the reviewed files at the review commit.
  • Severity follows the Quantix impact × likelihood matrix published in the methodology.

Technical references

  1. 1.EIP-4626: Tokenized Vaults (opens in a new tab)
  2. 2.OpenZeppelin ERC-4626: inflation attack and virtual offset (opens in a new tab)
  3. 3.CWE-682: Incorrect Calculation (opens in a new tab)
  4. 4.Uniswap v2 oracles: time-weighted average prices (opens in a new tab)
  5. 5.CWE-807: Reliance on Untrusted Inputs in a Security Decision (opens in a new tab)
  6. 6.CWE-862: Missing Authorization (opens in a new tab)
  7. 7.Solidity security considerations (opens in a new tab)
  8. 8.SWC-107: Reentrancy (opens in a new tab)
  9. 9.CWE-841: Improper Enforcement of Behavioral Workflow (opens in a new tab)
  10. 10.OpenZeppelin: writing upgradeable contracts (opens in a new tab)
  11. 11.EIP-1822: Universal Upgradeable Proxy Standard (opens in a new tab)
  12. 12.ERC-7201: Namespaced Storage Layout (opens in a new tab)
  13. 13.CWE-284: Improper Access Control (opens in a new tab)
  14. 14.CWE-778: Insufficient Logging (opens in a new tab)
  15. 15.SWC-103: Floating Pragma (opens in a new tab)

Quantix Technical Security Assessment QX-TSA-2026-001

Version 1.1, published 18 Aug 2026. A4 PDF generated from this assessment record.

Download Report

12

Report history

  1. 27 Jul 2026Review openedReviewed commit 4be1f0c9d2a7 in github.com/example-org/quillon-vaults
  2. 7 Aug 2026Review closedTwo reviewers, ten working days, plus a three-day remediation review
  3. After reviewRemediation reviewFixes checked at commit a91c7e2d04b3
  4. 18 Aug 2026Version 1.1 publishedQX-TSA-2026-001, A4 PDF generated from this record

Commission an assessment written to be verified.

Share the repository and the commit you plan to deploy. We reply with scoping questions and an estimate.