Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -119,6 +119,7 @@ operations page or guide with the exact commands.
reconcile with `make roles-check`. See [roles](docs/roles.md).
- **Configure v2 pool features.** [Rate limits](docs/operations/rate-limits.md),
[CCV](docs/operations/ccv.md), [hooks and allowlist](docs/operations/hooks-allowlist.md),
[policy engine](docs/operations/policy-engine.md),
[fees](docs/operations/fees.md), [finality](docs/operations/finality.md),
[dynamic config](docs/operations/dynamic-config.md).
- **LockRelease liquidity.** The rebalancer and ERC20 LockBox models, side by side, in
Expand Down
1 change: 1 addition & 0 deletions docs/guides/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ concept page, or a dedicated guide. Grouping is for navigation only.

- [Required verifiers (CCV) per lane](../operations/ccv.md).
- [Advanced Pool Hooks and the additional-CCV threshold](../operations/hooks-allowlist.md).
- [ACE Policy Engine on the hooks](../operations/policy-engine.md).
- [Per-lane token-transfer fees](../operations/fees.md).
- [Fast finality and fast-finality rate limits](../operations/finality.md).
- [Rate limits (set, pause, remove) and the version deltas](../operations/rate-limits.md).
Expand Down
3 changes: 3 additions & 0 deletions docs/operations/hooks-allowlist.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,9 @@ callers on hooks and lock boxes. Scripts under `script/configure/allowlist/` and
[`UpdateAuthorizedCallers`](../primitives/authorized-callers/UpdateAuthorizedCallers.md),
[`GetAuthorizedCallers`](../primitives/authorized-callers/GetAuthorizedCallers.md).

For the policy engine surface of the same hooks contract, see
[Policy engine](policy-engine.md).

## Deploy Advanced Pool Hooks

Use this for enhanced security features like allowlists, CCV management, policy engine integration, and
Expand Down
123 changes: 123 additions & 0 deletions docs/operations/policy-engine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
---
type: reference
---

# Policy engine

> After applying a change here, sync your declared source of truth (the project store) and reconcile it
> against live with `make doctor CHAIN=<chain>`: see
> [Applying config and reconciling with doctor](../config-architecture.md#applying-config-and-reconciling-with-doctor).
> For this page specifically the sync command is `make snapshot-chain CHAIN=<chain>`: `hooks.policyEngine`
> is a governance-critical `roles{}` field, and the old declaration fails `make roles-check` until it is
> re-snapshotted.

Connect an `AdvancedPoolHooks` contract to a [Chainlink ACE](https://docs.chain.link/ace) Policy Engine
on the same chain, or disconnect the engine. Scripts under `script/configure/policy-engine/`. Primitive
pages: [`SetPolicyEngine`](../primitives/policy-engine/SetPolicyEngine.md),
[`GetPolicyEngine`](../primitives/policy-engine/GetPolicyEngine.md).

The hook forwards each transfer to the engine for evaluation before the source pool locks or burns
tokens (`preflightCheck`) and before the destination pool releases or mints tokens
(`postflightCheck`). If a policy rejects the call, the hook reverts and the transfer does not proceed.
For the Platform-side workflow (target detection, the built-in `CCIP-AdvancedPoolHooks` contract type,
policy instances, protections, and extractor mappings), see
[Protect CCIP Token Pools with ACE](https://docs.chain.link/ace/guides/policy-manager/ccip-token-pools).

## Before you set an engine

Two preconditions must hold before the swap lands, or every transfer on the affected lanes stops:

- **Provision the engine for this hooks address first.** Extractors, policies, and the per-target
default all live on the engine, keyed by the hooks address; nothing carries over from the old
engine. With the ACE default of reject, an unprovisioned engine rejects every `ccipSend` from the
moment the swap lands (`PolicyRunRejected`). Provision the new engine (extractor, policies,
`setTargetDefaultPolicyAllow`) while the old engine (or none) is still attached, then swap.
- **Budget `destGasOverhead` for `postflightCheck` on every lane into this chain.** `postflightCheck`
runs inside `releaseOrMint` on the destination, within the gas the source pool quotes as
`destGasOverhead` for that lane. The FeeQuoter default is 90,000, and one policy alone can exceed it
(a local measurement of one `VolumePolicy` in `postflightCheck` runs ~97,000 gas; a live run with
Sanctions, KYC and Volume policies measured ~217,000). A short budget ends in an out-of-gas inside
the pool call, and the message goes to FAILURE with `TokenHandlingError(token, 0x)` (empty bytes,
unlike a real policy rejection, which carries `PolicyRunRejected`) and needs a manual execution.
Raise it on the source pool of every lane into the engine's chain with
[`UpdateTokenTransferFeeConfig`](fees.md) (`DEST_GAS_OVERHEAD=360000`, `DEST_BYTES_OVERHEAD` of at
least 32), and declare it in `lanes.<remote>.v2.feeConfig`. The larger overhead is charged to the
sender.

Also note the ACE version difference: from ACE 1.1.1 the engine's `run()` reverts `TargetNotAttached`
for a target that is not attached, while 1.0.0 does not gate on it. A local test against 1.0.0 can
therefore pass a flow a live 1.1.1+ engine rejects.

## Set the policy engine

Point the hooks at the engine deployed on the same chain. The hook calls `attach()` on the engine,
which starts ACE target detection. Only the hooks owner can call `setPolicyEngine`; when a Safe owns
the hooks, run it with `MODE=safe` (see [governance modes](../governance-modes.md)).

```bash
POOL_HOOKS=0x... \
POLICY_ENGINE=0x... \
forge script \
script/configure/policy-engine/SetPolicyEngine.s.sol \
--rpc-url \
$ETHEREUM_SEPOLIA_RPC_URL \
--account \
$KEYSTORE_NAME \
--broadcast
```

The zero address disconnects the engine and stops policy checks:

```bash
POOL_HOOKS=0x... \
POLICY_ENGINE=0x0000000000000000000000000000000000000000 \
forge script \
script/configure/policy-engine/SetPolicyEngine.s.sol \
--rpc-url \
$ETHEREUM_SEPOLIA_RPC_URL \
--account \
$KEYSTORE_NAME \
--broadcast
```

The script refuses two inputs before broadcasting: a same-address update (a silent no-op on the hook
that would report success) and an engine address with no code on this chain (the hook's `attach()` call
would revert with empty data).

`setPolicyEngine` detaches the old engine first and reverts `PolicyEngineDetachReverted` when the old
engine's `detach()` reverts. The recovery path is the same script with `ALLOW_FAILED_DETACH=true`,
which calls `setPolicyEngineAllowFailedDetach` on the hook:

```bash
POOL_HOOKS=0x... \
POLICY_ENGINE=0x... \
ALLOW_FAILED_DETACH=true \
forge script \
script/configure/policy-engine/SetPolicyEngine.s.sol \
--rpc-url \
$ETHEREUM_SEPOLIA_RPC_URL \
--account \
$KEYSTORE_NAME \
--broadcast
```

The old engine keeps the hook listed as attached after a forced move, so pointing back at it later
reverts `TargetAlreadyAttached` until that engine drops the hook.

After the swap, re-snapshot the declared roles: `make snapshot-chain CHAIN=<chain>` records
`hooks.policyEngine` in `roles{}`. Until then, `make roles-check` reports the moved governance slot,
which the [roles runbook](../roles.md#the-drift-response-runbook) says to treat as a potential
compromise.

## Get the policy engine

Reads the engine address currently set on the hooks contract, or reports that none is set (policy
checks disabled).

```bash
POOL_HOOKS=0x... \
forge script \
script/configure/policy-engine/GetPolicyEngine.s.sol \
--rpc-url \
$ETHEREUM_SEPOLIA_RPC_URL
```
18 changes: 18 additions & 0 deletions docs/primitives/_meta.json
Original file line number Diff line number Diff line change
Expand Up @@ -47,5 +47,23 @@
"INBOUND_RATE_LIMIT_RATE": "uint128 token-bucket refill rate (inbound).",
"INBOUND_RATE_LIMIT_ENABLED": "true/false; defaults to true when CAPACITY or RATE are set. false stands alone as a disable."
}
},
"SetPolicyEngine": {
"destructive": true,
"when_to_use": "Connect an AdvancedPoolHooks contract to an ACE Policy Engine on the same chain, or disconnect the engine with the zero address. The hook calls attach() on the new engine, which starts ACE target detection.",
"preconditions": "The executing account is the hooks owner (MODE=safe when a Safe owns it). The engine is deployed on the same chain and already provisioned for this hooks address: extractor, policies, and setTargetDefaultPolicyAllow. Every lane into this chain quotes a destGasOverhead that covers postflightCheck (UpdateTokenTransferFeeConfig).",
"postconditions": "getPolicyEngine returns the new engine. Run make snapshot-chain: hooks.policyEngine is a governance-critical roles{} field and the old declaration fails roles-check until re-snapshotted.",
"failure_modes": "DESTRUCTIVE: the zero address turns compliance checks off, and an unprovisioned engine rejects every transfer (PolicyRunRejected). An engine address with no code and a same-address update are refused before broadcasting. Reverts PolicyEngineDetachReverted when the old engine's detach() reverts; rerun with ALLOW_FAILED_DETACH=true. Pointing back at an engine left behind that way reverts TargetAlreadyAttached. Too little destGasOverhead fails inbound messages with TokenHandlingError(token, 0x).",
"inputs": {
"POLICY_ENGINE": "Address of the ACE Policy Engine on the same chain, or the zero address to disconnect the engine and stop policy checks.",
"POOL_HOOKS": "Address of the AdvancedPoolHooks contract. Resolves via the standard ladder: inline alias > {CHAIN}_POOL_HOOKS env > registry active.poolHooks.",
"ALLOW_FAILED_DETACH": "true calls setPolicyEngineAllowFailedDetach, tolerating a reverting detach() on the old engine (default false)."
}
},
"GetPolicyEngine": {
"when_to_use": "Read which ACE Policy Engine an AdvancedPoolHooks contract points at, or confirm none is set (policy checks disabled).",
"inputs": {
"POOL_HOOKS": "Address of the AdvancedPoolHooks contract. Resolves via the standard ladder: inline alias > {CHAIN}_POOL_HOOKS env > registry active.poolHooks."
}
}
}
35 changes: 34 additions & 1 deletion docs/primitives/catalog.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
"generatedBy": "script/docs/gen-primitives.mjs",
"note": "Generated from the script/ tree and the Makefile. Do not edit by hand; run `npm run docs:catalog`.",
"counts": {
"primitives": 69,
"primitives": 71,
"makeTargets": 31
},
"primitives": [
Expand Down Expand Up @@ -444,6 +444,39 @@
"AMOUNT"
]
},
{
"name": "GetPolicyEngine",
"script": "script/configure/policy-engine/GetPolicyEngine.s.sol",
"group": "policy-engine",
"description": "Script to read the ACE Policy Engine address currently set on an AdvancedPoolHooks contract.",
"modes": [
"read"
],
"read_only": true,
"writes_onchain": false,
"destructive": false,
"inputs": [
"POOL_HOOKS"
]
},
{
"name": "SetPolicyEngine",
"script": "script/configure/policy-engine/SetPolicyEngine.s.sol",
"group": "policy-engine",
"description": "Script to point an AdvancedPoolHooks contract at an ACE Policy Engine on the same chain, or disconnect the engine with the zero address.",
"modes": [
"eoa",
"safe"
],
"read_only": false,
"writes_onchain": true,
"destructive": true,
"inputs": [
"ALLOW_FAILED_DETACH",
"POLICY_ENGINE",
"POOL_HOOKS"
]
},
{
"name": "GetCurrentRateLimits",
"script": "script/configure/rate-limiter/GetCurrentRateLimits.s.sol",
Expand Down
5 changes: 5 additions & 0 deletions docs/primitives/index.md
Original file line number Diff line number Diff line change
Expand Up @@ -91,6 +91,11 @@ at [`catalog.json`](catalog.json). These pages are generated from the scripts an
- [AcceptOwnership](ownership/AcceptOwnership.md) - Completes a two-step ownership transfer initiated by TransferOwnership for any Ownable contract (a token pool, pool hooks, or a lockbox). _(write)_
- [TransferOwnership](ownership/TransferOwnership.md) - Initiates a two-step ownership transfer for any Ownable contract (a token pool, pool hooks, or a lockbox). _(write)_

## policy-engine

- [GetPolicyEngine](policy-engine/GetPolicyEngine.md) - Script to read the ACE Policy Engine address currently set on an AdvancedPoolHooks contract. _(read-only)_
- [SetPolicyEngine](policy-engine/SetPolicyEngine.md) - Script to point an AdvancedPoolHooks contract at an ACE Policy Engine on the same chain, or disconnect the engine with the zero address. _(write, destructive)_

## rate-limiter

- [GetCurrentRateLimits](rate-limiter/GetCurrentRateLimits.md) - Reads and displays the current rate limiter state for a TokenPool, compatible with v1 and v2 pools. _(read-only)_
Expand Down
32 changes: 32 additions & 0 deletions docs/primitives/policy-engine/GetPolicyEngine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
---
name: GetPolicyEngine
script: script/configure/policy-engine/GetPolicyEngine.s.sol
group: policy-engine
type: reference
modes: [read]
read_only: true
writes_onchain: false
destructive: false
---

# GetPolicyEngine

Script to read the ACE Policy Engine address currently set on an AdvancedPoolHooks contract.

**When to use.** Read which ACE Policy Engine an AdvancedPoolHooks contract points at, or confirm none is set (policy checks disabled).

## Inputs

| Env var | Description |
| --- | --- |
| `POOL_HOOKS` | Address of the AdvancedPoolHooks contract. Resolves via the standard ladder: inline alias > {CHAIN}_POOL_HOOKS env > registry active.poolHooks. |

## Reference

- Script: [`script/configure/policy-engine/GetPolicyEngine.s.sol`](../../../script/configure/policy-engine/GetPolicyEngine.s.sol)
- Modes: read
- Read-only: true | Writes on-chain: false | Destructive: false

_This page is generated from the script by `script/docs/gen-primitives.mjs`. Edit the script's
`@notice` for the description, or `docs/primitives/_meta.json` for the authored context; do not edit
this file by hand._
46 changes: 46 additions & 0 deletions docs/primitives/policy-engine/SetPolicyEngine.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
---
name: SetPolicyEngine
script: script/configure/policy-engine/SetPolicyEngine.s.sol
group: policy-engine
type: reference
modes: [eoa, safe]
read_only: false
writes_onchain: true
destructive: true
---

# SetPolicyEngine

Script to point an AdvancedPoolHooks contract at an ACE Policy Engine on the same chain, or disconnect the engine with the zero address.

**When to use.** Connect an AdvancedPoolHooks contract to an ACE Policy Engine on the same chain, or disconnect the engine with the zero address. The hook calls attach() on the new engine, which starts ACE target detection.

## Inputs

| Env var | Description |
| --- | --- |
| `ALLOW_FAILED_DETACH` | true calls setPolicyEngineAllowFailedDetach, tolerating a reverting detach() on the old engine (default false). |
| `POLICY_ENGINE` | Address of the ACE Policy Engine on the same chain, or the zero address to disconnect the engine and stop policy checks. |
| `POOL_HOOKS` | Address of the AdvancedPoolHooks contract. Resolves via the standard ladder: inline alias > {CHAIN}_POOL_HOOKS env > registry active.poolHooks. |

## Preconditions

The executing account is the hooks owner (MODE=safe when a Safe owns it). The engine is deployed on the same chain and already provisioned for this hooks address: extractor, policies, and setTargetDefaultPolicyAllow. Every lane into this chain quotes a destGasOverhead that covers postflightCheck (UpdateTokenTransferFeeConfig).

## Postconditions

getPolicyEngine returns the new engine. Run make snapshot-chain: hooks.policyEngine is a governance-critical roles{} field and the old declaration fails roles-check until re-snapshotted.

## Known failure modes

DESTRUCTIVE: the zero address turns compliance checks off, and an unprovisioned engine rejects every transfer (PolicyRunRejected). An engine address with no code and a same-address update are refused before broadcasting. Reverts PolicyEngineDetachReverted when the old engine's detach() reverts; rerun with ALLOW_FAILED_DETACH=true. Pointing back at an engine left behind that way reverts TargetAlreadyAttached. Too little destGasOverhead fails inbound messages with TokenHandlingError(token, 0x).

## Reference

- Script: [`script/configure/policy-engine/SetPolicyEngine.s.sol`](../../../script/configure/policy-engine/SetPolicyEngine.s.sol)
- Modes: eoa, safe
- Read-only: false | Writes on-chain: true | Destructive: true

_This page is generated from the script by `script/docs/gen-primitives.mjs`. Edit the script's
`@notice` for the description, or `docs/primitives/_meta.json` for the authored context; do not edit
this file by hand._
Loading
Loading