# PPYieldTokenZap contract

> Stateless underlying-to-share conversion and the yield stack's only Aave touchpoint: exact-share minting with a 2-unit rounding buffer and balance-delta accounting throughout.

PPYieldTokenZap is a stateless helper that converts between an underlying token and a [PPYieldToken](/protocol/contracts/pp-yield-token) share in one call: it supplies the underlying to Aave, wraps the resulting aToken into shares, and does the reverse on the way out. It is the *only* place in the yield stack that calls Aave, which is what keeps [PPRouter](/protocol/contracts/pp-router) protocol-agnostic. One zap is deployed per wrapper by [`PPYieldTokenZapFactory`](/protocol/contracts/yield-factories).

## What it does

The zap holds no funds between transactions and has no owner. Its four entry points are the two directions of the same conversion, plus an exact-share variant and a quote:

`depositUnderlying(uint256 _underlyingAmount, address _receiver) → shares` Pulls the underlying from the caller, supplies it to Aave with the zap as the `onBehalfOf`, wraps exactly the aToken that supply produced into the wrapper, and mints the shares to `_receiver`.
`buyFixedYieldToken(uint256 _shares, address _receiver, uint256 _maxUnderlyingIn) → underlyingIn` Mints an exact share count. Sizes the underlying as `previewMint(_shares) + 2`, reverts `PPYieldTokenZap_SlippageExceeded` if that exceeds `_maxUnderlyingIn`, supplies to Aave, requires the aToken received covers the mint (`PPYieldTokenZap_InsufficientWrap`), mints exactly `_shares` to `_receiver`, and forwards any residual aToken dust from this call to `_receiver`. This is the leg [PPRouter](/protocol/contracts/pp-router)`.depositExactShares` uses.
`redeemToUnderlying(uint256 _shares, address _receiver) → underlyingOut` Pulls the shares from the caller, redeems them at the wrapper for aToken, withdraws that aToken from Aave, and pays the underlying to `_receiver`. This is also the one-call exit for anyone holding loose shares, for example after a [ragequit](/operations/ragequit) of a yield note.
`quoteBuy(uint256 _shares) → underlyingNeeded` `previewMint(_shares) + 2`, the exact figure `buyFixedYieldToken` will pull. `PPRouter.quote` is built on it.

## The 2-unit supply buffer

A single Aave `supply(x)` credits either `x` or `x - 1` units of aToken because of the scaled-balance round trip, so an exact mint that supplied precisely `previewMint(shares)` could come up one unit short and revert. The zap over-supplies by `_SUPPLY_ROUNDING_BUFFER = 2` on every exact mint. That buffer is the only value a depositor forfeits per exact mint, and it is not kept: whatever aToken is left after the mint is forwarded to the receiver as dust (a router receiving it sweeps it later with `skim`).

## Balance-delta accounting

Every amount the zap credits or withdraws is measured as the *change* in its own aToken balance around the Aave or wrapper call, never as the absolute `balanceOf`. `depositUnderlying` wraps only the aToken *this* supply produced; `redeemToUnderlying` withdraws only the aToken *this* redeem produced; `buyFixedYieldToken` checks the wrap and forwards dust against a pre-supply baseline.

This is what makes the zap safe to leave permissionless. If it read absolute balances, an attacker could donate aToken to the zap and have the next caller sweep the donation into their own shares or their own payout. With deltas, a pre-donated balance is simply left untouched, and the zap never accrues value beyond such donations.

## Immutables

`VAULT() → address` The [PPYieldToken](/protocol/contracts/pp-yield-token) wrapper this zap serves.
`POOL() → address` The Aave v3 `Pool` used for `supply` and `withdraw`.
`UNDERLYING() → address` The token supplied to Aave, e.g. USDC.
`A_TOKEN() → address` `VAULT.asset()`, the aToken the wrapper is backed by.

[`RelaySwaps`](/protocol/contracts/relay-swaps) reads `VAULT`, `A_TOKEN`, and `UNDERLYING` when it allowlists a zap, and PPRouter's constructor checks that its zap's `VAULT` and `UNDERLYING` match its own, so a misconfigured pairing fails at deploy time rather than on a user's withdrawal.

## Errors you'll see

`PPYieldTokenZap_ZeroAddress()` Constructor received a zero vault, pool, or underlying.
`PPYieldTokenZap_ZeroAmount()` A zero underlying amount or share count was passed.
`PPYieldTokenZap_SlippageExceeded(uint256 required, uint256 maxUnderlyingIn)` The exact mint needs more underlying than the caller's cap. Re-quote and retry; the rate moved between quote and execution.
`PPYieldTokenZap_InsufficientWrap(uint256 received, uint256 needed)` Aave credited less aToken than the mint needs, even with the buffer. Not expected on a standard reserve; indicates unusual index rounding for that asset.

## Events

`ZappedIn(address sender, address receiver, uint256 underlyingIn, uint256 shares)` Emitted by `depositUnderlying`.
`ZappedInExact(address sender, address receiver, uint256 underlyingIn, uint256 shares)` Emitted by `buyFixedYieldToken`.
`ZappedOut(address sender, address receiver, uint256 shares, uint256 underlyingOut)` Emitted by `redeemToUnderlying`.

Source: `v2-monorepo/packages/contracts/src/contracts/PPYieldTokenZap.sol`
