ChainGuard investigation report

lending-protocol (fixture)

Demo Analysis. Pre-recorded fixture data; not produced by a live run.

Source
contracts/vulnerable/multi_contract
Completed
Sun, 20 Sep 2026 14:08:00 GMT
Static analysis
slither · fixture
AI investigation
fixture

Executive summary

ChainGuard identified 2 potential attack chain(s) and 8 potential finding(s) across 6 contracts and 13 functions. 4 findings connect into chains, 3 are isolated, and 1 could not be validated.

These are potential issues derived from static analysis and evidence-validated AI reasoning. They are not confirmed exploits and require manual verification.

Attack chains

CG-001CriticalSupportedStrongly supported · confidence 81%

Potential oracle takeover leading to over-borrowing

An unprotected price-source setter lets any caller point SpotPriceOracle at a pool they control. LendingVault then values collateral at the attacker-chosen price and lends against it.

Entry point: SpotPriceOracle.setPool() contracts/SpotPriceOracle.sol:L18–21

  1. 1.Deploy a malicious poolAttacker actioncontracts/IPool.sol:L5 · evidence E-15
  2. 2.Replace the oracle price sourceVulnerabilitycontracts/SpotPriceOracle.sol:L18–21 · evidence E-01, E-11
  3. 3.`pool` now points to the attacker contractState changecontracts/SpotPriceOracle.sol:L20 · evidence E-02
  4. 4.Deposit nominal collateralAttacker actioncontracts/LendingVault.sol:L25–27 · evidence E-19
  5. 5.Oracle returns an inflated spot priceExternal dependencycontracts/SpotPriceOracle.sol:L25 · evidence E-03
  6. 6.Collateral valued at the manipulated priceContract functioncontracts/LendingVault.sol:L30 · evidence E-04
  7. 7.Borrow limit inflated beyond real collateralVulnerabilitycontracts/LendingVault.sol:L38 · evidence E-05
  8. 8.Vault debt-token liquidity drainedImpactcontracts/LendingVault.sol:L40 · evidence E-06

Impact. An attacker could borrow up to the vault’s entire debt-token balance against a nominal ETH deposit, leaving the protocol with unrecoverable bad debt.

How to break the chain

  1. 1. Restrict setPool to the owner contracts/SpotPriceOracle.sol:L18–21

    Add an owner check to setPool (for example, require(msg.sender == owner) or an onlyOwner modifier) and consider a timelock for price-source changes.

  2. 2. Use a manipulation-resistant price contracts/SpotPriceOracle.sol:L24–28

    Replace spot reserves with a TWAP or an aggregated oracle feed, with staleness and deviation checks.

  3. 3. Bound oracle input in the vault contracts/LendingVault.sol:L29–31

    Reject prices outside configured min/max bounds or beyond a per-block deviation before using them in borrow limits.

CG-002HighSupportedModerately supported · confidence 74%

Potential cross-function reentrancy enabling borrow against withdrawn collateral

withdrawCollateral hands execution to the caller before reducing collateral. A contract caller can re-enter borrow() and borrow against collateral that is already leaving the vault.

Entry point: LendingVault.withdrawCollateral() contracts/LendingVault.sol:L43–52

  1. 1.Withdraw collateral from a contract accountAttacker actioncontracts/LendingVault.sol:L43–52 · evidence E-20
  2. 2.ETH sent before state updateVulnerabilitycontracts/LendingVault.sol:L48 · evidence E-07, E-12
  3. 3.Re-enter borrow() from receive()Attacker actioncontracts/LendingVault.sol:L37–41 · evidence E-21
  4. 4.Stale collateral inflates the borrow limitState changecontracts/LendingVault.sol:L30 · evidence E-14
  5. 5.Collateral decremented after borrowState changecontracts/LendingVault.sol:L51 · evidence E-13
  6. 6.Undercollateralized debt positionImpactcontracts/LendingVault.sol:L43–52 · evidence E-13, E-06

Impact. Each withdraw/borrow cycle can extract debt tokens backed by collateral that has already left the vault.

How to break the chain

  1. 1. Apply checks-effects-interactions contracts/LendingVault.sol:L43–52

    Decrement collateral[msg.sender] before sending ETH.

  2. 2. Share a reentrancy guard contracts/LendingVault.sol:L37–41

    Apply a nonReentrant modifier to both withdrawCollateral and borrow.

Findings

IDSeverityFindingLocationRelationship
F-001HighUnprotected oracle price-source setterAccess controlcontracts/SpotPriceOracle.sol:L18–21Part of attack chain
F-002HighCollateral priced from single-pool spot reservesOracle / price manipulationcontracts/SpotPriceOracle.sol:L24–28Part of attack chain
F-004HighCross-function reentrancy from withdrawCollateral into borrowReentrancycontracts/LendingVault.sol:L43–52Part of attack chain
F-003MediumBorrow limit trusts an unbounded oracle priceLogic / arithmeticcontracts/LendingVault.sol:L37–41Part of attack chain
F-005MediumUnchecked return value of send() in claimUnchecked external callcontracts/RewardDistributor.sol:L22–26Isolated
F-006MediumTreasury authorization relies on tx.originAccess controlcontracts/Treasury.sol:L12–15Isolated
F-008MediumOracle reserves may be flash-loan manipulableFlash-loan surfacecontracts/SpotPriceOracle.sol:L25Unverified
F-007InformationalFloating compiler pragmaOthercontracts/LendingVault.sol:L2Isolated

Methodology & limitations

  1. Repository ingestion with path-traversal, size, and file-type guards. Repository code is never executed.
  2. Deterministic mapping of contracts, functions, external calls, state access, and oracle dependencies.
  3. Static analysis with Slither where available.
  4. AI investigation over a bounded context package; outputs are treated as candidates.
  5. Evidence validation of every contract, function, file, and line reference against the repository map.
  • This is a Demo Analysis built from a fixture repository. It is not a live scan.
  • ChainGuard reports potential attack chains from static evidence; it does not confirm runtime exploitability.
  • Absence of findings is not proof of security.