# Yield factories

> The three owner-gated factories that deploy one wrapper, zap, and router per Aave asset, check aToken genuineness against one trusted pool, and seed each wrapper with locked dead shares.

Three owner-gated factories deploy and track the yield stack, one instance per wrapper: `PPYieldTokenFactory` for the [PPYieldToken](/protocol/contracts/pp-yield-token) wrappers, `PPYieldTokenZapFactory` for their [zaps](/protocol/contracts/pp-yield-token-zap), and `PPRouterFactory` for their [routers](/protocol/contracts/pp-router). They are kept separate so that none embeds another's creation bytecode (each stays clear of the contract-size limit) and each has one job; the deploy script orchestrates the three in order.

All three are `Ownable` and all three override `renounceOwnership` to revert, so their only owner-gated function can never be permanently bricked. Ownership is transferable but never droppable. Every instance they create is immutable (no proxy): "upgrading" a wrapper, zap, or router means deploying a new one through the factory and re-pointing relayer and SDK configuration, never changing code in place.

## Genuineness

Each factory is bound to one trusted Aave v3 `Pool` and only creates instances for that pool's aTokens. The check is `IAToken(aToken).POOL() == POOL`, and the underlying is read from `IAToken(aToken).UNDERLYING_ASSET_ADDRESS()` rather than taken from the caller. A wrapper that lied about its `asset()` would at worst produce a useless zap or router (owner self-harm), never a working instance over a spoofed token, and the wrapper seed below goes through the real `POOL.supply`, which only a genuine aToken satisfies.

## PPYieldTokenFactory

`createWrapper(address _aToken, uint256 _feeBps, address _admin, string _name, string _symbol, uint256 _seedAssets) → wrapper` Owner only. Requires the aToken belongs to `POOL`, no wrapper exists for it yet, and `_seedAssets != 0`. Deploys the wrapper with the given fee, admin, name, and symbol, then seeds it: the factory supplies `_seedAssets` of the underlying to Aave, deposits the resulting aToken into the new wrapper, and sends the minted shares to `0xdEaD`, where they are locked forever.
`wrapperFor(address _aToken) → address` The wrapper deployed for an aToken, or zero.
`POOL() → address` The trusted Aave pool.

The dead-share seed is the belt to the decimals-offset braces: a wrapper is never empty, so the ERC-4626 first-depositor inflation attack is neutralised regardless of the asset's decimals. A wrapper deployed directly, bypassing the factory, loses the seed and keeps only the offset.

## PPYieldTokenZapFactory

`createZap(address _vault) → zap` Owner only. Reads the wrapper's aToken, requires it belongs to `POOL` and that no zap exists for the wrapper, derives the underlying from the aToken, and deploys a zap over `(vault, POOL, underlying)`.
`zapFor(address _vault) → address` The zap for a wrapper, or zero.
`zapsCount() → uint256` / `allZaps(uint256 i) → address` Enumeration in deployment order.

Zaps hold no funds, so this factory grants no power over user assets.

## PPRouterFactory

`createRouter(address _vault, address _zap) → router` Owner only. Deploys an ERC-20-mode router for the wrapper: the audited USDC-style flow.
`createRouterNative(address _vault, address _zap) → router` Owner only. Deploys a native-mode router for a wrapper over a wrapped-native aToken (aWETH, giving ppETH), so users deposit and receive the native coin. See [PPRouter](/protocol/contracts/pp-router).
`routerFor(address _vault) → address` The router for a wrapper, or zero. One router per wrapper regardless of mode.
`routersCount() → uint256` / `allRouters(uint256 i) → address` Enumeration in deployment order.
`POOL()`, `ENTRYPOINT()`, `POOL_VAULT()`, `DUST_RECIPIENT()` The Aave pool every router is checked against, and the entrypoint, vault, and dust recipient every router is given.

Both create functions share one internal path: they require the wrapper's aToken belongs to `POOL`, that no router exists for the wrapper, derive the underlying, and construct the router. The router's own constructor then verifies the supplied zap wraps exactly that `(vault, underlying)` pair, and a native-mode router additionally probes the underlying with a zero-value `IWETH9.deposit`, so a mismatched or non-WETH configuration fails at deploy time rather than on a user's first transaction.

## Errors you'll see

`PP*Factory_NotAavePoolAToken()` The aToken's `POOL()` is not this factory's pool.
`PP*Factory_AlreadyDeployed()` An instance already exists for that aToken or wrapper.
`PPYieldTokenFactory_ZeroSeed()` `createWrapper` was called with `_seedAssets == 0`.
`PP*Factory_RenounceDisabled()` Someone called `renounceOwnership`.

Source: `v2-monorepo/packages/contracts/src/contracts/PPYieldTokenFactory.sol`, `PPYieldTokenZapFactory.sol`, `PPRouterFactory.sol`
