Skip to content
577 changes: 577 additions & 0 deletions AAVE_V3_ASSERTION_REVIEW.md

Large diffs are not rendered by default.

77 changes: 77 additions & 0 deletions examples/aave/AAVE_V4_ORACLE_ASSERTION_DEPLOYMENT.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,77 @@
# Aave V4 consumed-oracle assertion deployment

`AaveV4EthereumMainSpokeOracleAssertion` is pinned to Ethereum Main Spoke state
at block 25,646,732 and Aave V4 release `v0.5.11`.

## Constructor

```solidity
new AaveV4EthereumMainSpokeOracleAssertion(
maxTraceCalls,
deviationBpsByReserveId,
extraVerifiedConfigSlots
);
```

- `maxTraceCalls`: transaction-wide maximum for committed
`getReservePrice` calls and configured-source `latestAnswer` calls. `64` is a
practical initial value for the 14-reserve Main Spoke and two full price
sweeps. Lower values reduce worst-case work but reject larger multicalls.
- `deviationBpsByReserveId`: 14 tolerances ordered by reserve ID:
WETH, wstETH, weETH, WBTC, cbBTC, AAVE, LINK, USDC, USDT, EURC, RLUSD, USDG,
frxUSD, GHO. Zero means exact equality with the PreTx price. A value of 100
permits `[99%, 101%]`, with conservative rounding at both bounds.
- `extraVerifiedConfigSlots`: additional `(target, slot)` guards. The wrapper
already includes all 22 verified mutable Chainlink/CAPO routing slots in the
pinned source graph. Do not add guesses; document the upstream layout and
deployed bytecode for every extra slot.

The constructor rejects a zero target, zero trace bound, more than 64 policies,
more than 128 total config guards, duplicate guards, duplicate direct sources,
non-contiguous reserve IDs, zero assets/sources, and tolerances at or above
10,000 bps.

## Adoption checklist

Before activation:

1. Confirm chain ID 1 and Main Spoke
`0x94e7A5dCbE816e498b89aB752661904E2F56c485`.
2. Re-read the ERC-1967 implementation slot and require
`0xABd0E26FE17BDe4F1f1187Ed8aA80C274E03D8b5`.
3. Require `ORACLE()` to equal
`0x99B2B6CEa9C3D2fd8F4d90f86741C44B212a6127`, oracle `spoke()` to point back
to Main Spoke, and oracle decimals to equal 8.
4. Re-read all 14 reserves and direct sources and compare them with the
production wrapper.
5. Review the source graph and built-in config slots against verified deployed
source. Rebuild the wrapper after any upstream migration.
6. Choose asset-specific tolerances. Zero is appropriate only when legitimate
within-transaction source movement is impossible or should be blocked.
7. Run the focused PCL suite with `-vvvv`; confirm parent/child call IDs and
outputs are still exposed as documented.
8. Measure a worst-case multicall under the production executor. The current
14-reserve/two-sweep fixture measures 2,881,782 gas.

At runtime, the assertion validates policy completeness and exact PreTx
reserve/source identity before accepting a committed price read. A legitimate
reserve addition, source update, proxy rotation, CAPO parameter update, or
Spoke upgrade intentionally blocks risk-sensitive operations until a reviewed
replacement assertion is deployed.

## Generic deployments

`AaveV4OracleConsumptionAssertion` can protect another verified V4 Spoke, but
the caller must provide:

- the exact Spoke adopter;
- the oracle selected by that implementation;
- the expected Spoke implementation;
- a complete reserve policy with unique direct sources; and
- verified storage guards for every mutable router, proxy, adapter, fallback,
or provider slot that can change a consumed price.

Do not copy the Ethereum Main Spoke asset list, sources, implementation, or
storage guards to another Spoke or chain. See the accompanying
[research note](research/aave-v4-oracle-consumption-protection-2026-07-30.md)
for the exact source and trace evidence.
52 changes: 51 additions & 1 deletion examples/aave/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,58 @@ FOUNDRY_PROFILE=aave forge build
- AaveV3HorizonHelpers.sol
- AaveV3HorizonInterfaces.sol
- AaveV3HorizonOracleAssertion.sol
- AaveV3HorizonReserveBackingAssertion.sol
- AaveV3HorizonReserveBackingAssertion.sol (quarantined: token-only mutations do not dispatch a Pool-adopter trigger)
- AaveV4Helpers.sol
- AaveV4HubAccountingAssertion.sol
- AaveV4HubFlowRateCircuitBreaker.sol (quarantined: net-flow dispatch can miss directional reversals)
- AaveV4Interfaces.sol
- AaveV4OracleConsumptionAssertion.sol
- AaveV4OracleConsumptionHelpers.sol
- AaveV4SpokeRiskAssertion.sol

## Aave v3 Horizon oracle guard

`AaveV3HorizonOracleAssertion` checks the exact `AaveOracle.getAssetPrice`
returns consumed by successful risk-sensitive Pool operations against per-asset
PreTx baselines. It also rejects temporary provider, source, and fallback
changes, including configuration writes restored before transaction end.

Deployment must configure an `AssetPolicy` for every active reserve whose price
the Pool can consume. Policies with zero deviation require exact same-transaction
price stability; nonzero tolerances should be asset-specific and empirically
calibrated. `maxTraceCalls` is a fail-closed bound on traced provider, oracle, and
source calls.

The source/fallback storage guards are pinned to the Aave v3.3 `AaveOracle`
layout (`assetsSources` mapping slot 0 and `_fallbackOracle` slot 1). Re-verify
those slots before adopting the assertion against a different oracle
implementation or storage layout.

## Aave v4 consumed-oracle guard

`AaveV4OracleConsumptionAssertion` checks every committed Spoke-to-AaveOracle
price return against a per-reserve PreTx envelope. It maps the exact parent
oracle output through its direct `latestAnswer()` child, rejects routing and
adapter write-and-restore sequences, validates complete reserve policy, and
uses one bounded transaction-end scan for nested and multicall flows.

`AaveV4EthereumMainSpokeOracleAssertion` pins the live Ethereum Main Spoke,
implementation, oracle, 14 reserves, direct sources, and 22 verified mutable
source-graph slots at block 25,646,732.

See the
[deployment guide](AAVE_V4_ORACLE_ASSERTION_DEPLOYMENT.md) and
[pinned research and trace analysis](research/aave-v4-oracle-consumption-protection-2026-07-30.md).

## Aave v4 flow-rate calibration

The staged Core and Prime Hub circuit breakers define policies for WBTC, USDG, and
wstETH, the three highest-TVL Aave v4 assets in the 2026-07-30 DefiLlama
snapshot. Their 24-hour cumulative-flow and 10-second peak-rate limits use a
20% buffer over the maximum observed values in the preceding 30 days. Their
triggers remain unarmed until absolute directional or rate-native dispatch can
cover reversals inside a net-flow window.

See
[aave-v4-flow-rate-calibration-2026-07-30.md](research/aave-v4-flow-rate-calibration-2026-07-30.md)
Comment thread
mateo-mro marked this conversation as resolved.
for the block range, measurements, and operational caveats.
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# Aave v4 flow-rate circuit-breaker calibration

Snapshot date: 2026-07-30<br>
Chain: Ethereum mainnet<br>
Historical block range: 25,430,974–25,646,159<br>
Lookback: 30 days

## Asset selection

The three assets were selected from DefiLlama's aggregate Aave v4 token TVL:

| Rank | Asset | Aggregate token TVL |
| --- | --- | ---: |
| 1 | WBTC | $52.35m |
| 2 | USDG | $30.84m |
| 3 | wstETH | $29.40m |

DefiLlama derives Aave v4 TVL from ERC20 balances held by the Core, Plus, and
Prime Hubs. WBTC and wstETH are split between Core and Prime, so both Hubs need
their own adopter-scoped watchers. USDG is held only by Core.

Sources:

- <https://defillama.com/protocol/aave-v4>
- <https://api.llama.fi/protocol/aave-v4>
- <https://github.com/DefiLlama/DefiLlama-Adapters/blob/main/projects/aave-v4/index.js>

## Methodology

For each Hub/token pair:

1. Query every ERC20 `Transfer` to and from the Hub during the 30-day range.
2. Reconstruct the Hub balance at the beginning of the range from the current
balance and the net transfer flow.
3. Calculate the maximum rolling 24-hour net directional flow as basis points
of the Hub balance immediately before the window's first transfer.
4. Bucket net flow into 10-second intervals and calculate the peak flow rate as
basis points of the Hub balance per second.
5. Set each production limit to `ceil(observed maximum × 1.20)`.

This intentionally calibrates to the maximum observed window rather than the
30-day daily average. A breaker set from the average would have rejected
legitimate historical spikes.

The Phylax cumulative watcher measures net flow: inflows offset outflows and
vice versa. It does not cap gross volume. The peak-rate signal is experimental
and comes from the same 10-second buckets.

## Results

| Hub | Asset | Current balance | 30d gross in | 30d gross out | Max 24h net in | In limit | Max 24h net out | Out limit | Peak in rate | In-rate limit | Peak out rate | Out-rate limit |
| --- | --- | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: | ---: |
| Core | WBTC | 670.9402 | 129.0485 | 20.8481 | 986.44 bps | 1,184 bps | 91.41 bps | 110 bps | 39.32 bps/s | 48 bps/s | 7.22 bps/s | 9 bps/s |
| Core | USDG | 30.6733m | 68.6104m | 61.5900m | 4,329.38 bps | 5,196 bps | 4,472.00 bps | 5,367 bps | 438.64 bps/s | 527 bps/s | 128.27 bps/s | 154 bps/s |
| Core | wstETH | 8,490.4974 | 4,365.9474 | 1,416.9640 | 1,587.63 bps | 1,906 bps | 776.51 bps | 932 bps | 47.57 bps/s | 58 bps/s | 77.62 bps/s | 94 bps/s |
| Prime | WBTC | 136.7563 | 57.6882 | 47.4818 | 1,750.94 bps | 2,102 bps | 2,026.00 bps | 2,432 bps | 123.91 bps/s | 149 bps/s | 148.27 bps/s | 178 bps/s |
| Prime | wstETH | 3,840.5454 | 2,142.8947 | 1,233.7078 | 2,903.51 bps | 3,485 bps | 757.23 bps | 909 bps | 175.60 bps/s | 211 bps/s | 60.15 bps/s | 73 bps/s |

The observed maxima used for these constants are checked in as
`aave-v4-flow-rate-observed-maxima.csv`; `calculate-aave-v4-flow-limits.py`
recomputes every limit as `ceil(observed × 1.20)`. The table and chart were
reconciled to that artifact, notably for Core USDG and Prime WBTC outflow.

## Operational notes

- The assertion uses a 1 bps cumulative dispatch floor so the custom rate check
executes well before any calibrated limit. The policy trips if either the
cumulative limit or peak-rate limit is exceeded.
- The trigger is currently unarmed because net-flow dispatch can fail to select
the directional rate assertion after opposite-direction flow in the same
window. Keep this policy staged until absolute directional or rate-native
dispatch is available.
- Apply the Core assertion to the Core Hub and the Prime companion assertion to
the Prime Hub. Adopting either assertion on another address fails explicitly.
- Recalibrate before production rollout and after material cap, asset-mix, or
flow-regime changes. Thirty days is a useful initial sample, not a permanent
risk parameter.
- `inflowRate()` and `outflowRate()` require
`AssertionSpec.Experimental`; the public Phylax docs describe Experimental as
unrestricted and potentially untested. This draft should remain staged until
the runtime support and production policy are confirmed.
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
hub,asset,in_window_bps,out_window_bps,in_peak_rate_bps,out_peak_rate_bps
Core,WBTC,986.44,91.41,39.32,7.22
Core,USDG,4329.38,4472.00,438.64,128.27
Core,wstETH,1587.63,776.51,47.57,77.62
Prime,WBTC,1750.94,2026.00,123.91,148.27
Prime,wstETH,2903.51,757.23,175.60,60.15
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Loading