# PrizeVault

`contracts/src/PrizeVault.sol`

The only contract that holds user principal — and deliberately not our own
design. It is a **near-byte-faithful fork of PoolTogether V5's
`PrizeVault.sol`** (MIT; audited by Code4rena 2024 and Macro; running in
production over Morpho vaults — the same underlying pairing Stockpot uses).

| | |
|---|---|
| Upstream | `GenerationSoftware/pt-v5-vault` @ `b8226ab5` (vendored unmodified at `contracts/lib/pt-v5-vault/`) |
| Review the fork | `diff lib/pt-v5-vault/src/PrizeVault.sol src/PrizeVault.sol` |
| Delta policy | **Never modify the audited accounting.** Dust collection, yield buffer, debt/yield math, deposit/withdraw paths, and loss handling are byte-identical. |
| Companions | `TwabController`, `TwabERC20`, `TwabRewards` — PoolTogether V5, **unmodified**, pinned at the audited versions |
| Upgradeability | **None. No proxy, ever.** Stockpot cannot upgrade, replace, or take control of this contract. |

## Shares are 1:1

`previewDeposit(x) == x` and `previewMint(x) == x`, always. A share
represents exactly one deposited USDG unit and never appreciates — depositors
always withdraw exactly what they deposited (down to the last wei, via the
upstream dust-collection strategy and yield buffer). Depositor earnings are
paid separately as claimable interest (below), not through share price.

If the underlying yield vault ever loses assets, the vault enters the
audited loss state: new deposits shut off (`maxDeposit` returns 0, deposits
revert `LossyDeposit`) and withdrawals become proportional to remaining
assets. Yield can never be skimmed in a loss state.

## Yield accounting and the 20/80 split

```
totalDebt        = totalSupply + yieldFeeBalance     // what the vault owes
totalYield       = totalAssets − totalDebt
availableYield   = totalYield − yieldBuffer          // skimmable portion
```

The keeper (the `liquidationPair` role, kept under its upstream name to
minimize the diff) pulls the pot's share via `transferTokensOut`. The
audited fee mechanism (`yieldFeePercentage = 20%`) accrues the depositor
share in the same call:

* **80%** → withdrawn to the keeper, converted to ETH, and sent to the
  [DrawManager](/contracts/draw-manager) pot via `fundPot`.
* **20%** → accrues as `yieldFeeBalance`; the rewards distributor
  (`yieldFeeRecipient`) claims it as vault shares (`claimYieldFeeShares`)
  and streams it through **TwabRewards** as weekly claimable interest,
  pro-rata by deposit TWAB.

The skim is bounded by `availableYield` — the audited accounting makes
taking principal structurally impossible, not just forbidden.

## TWAB shares

The vault's share token is PT's `TwabERC20`: balances live in the
`TwabController` (1-day periods), so every transfer checkpoints a
time-weighted observation. Draw weights read straight from chain state — see
[Eligibility & Odds](/protocol/eligibility).

## Stockpot deltas (complete list)

Each is marked `Stockpot delta:` in source; `contracts/README.md` carries the
authoritative list.

1. **Prize-pool decoupling.** Upstream's `Claimable`/hook machinery and
   `PrizePool` coupling removed — draws run through Stockpot's DrawManager.
   The `TwabController` is a direct constructor parameter.
2. **Ownership.** PT's `owner-manager` Ownable replaced with OpenZeppelin
   `Ownable2Step` (the PT lib carries GPL-3.0 SPDX headers; same two-step
   semantics).
3. **Deposit cap.** `depositCap` (in assets; shares are 1:1) enforced in
   `maxDeposit` and on every deposit; owner-adjustable via `setDepositCap`.
   Cap-to-zero pauses new deposits. The cap never limits withdrawals or
   interest accrual. **$1M at launch**; an audit of Stockpot's deployment is
   the gate to an uncapped vault.
4. **Reward-token sweep.** `sweep(token, recipient)` (owner) recovers tokens
   that land on the vault address (e.g. any future holder-paid reward
   program). The asset, the yield vault's shares, and the vault's own shares
   are protected (`SweepTokenProtected`).
5. **Roles repurposed, mechanics unchanged.** `liquidationPair` = the
   keeper's yield skimmer; `yieldFeeRecipient` = the interest distributor;
   prize-pool-coupled `verifyTokensIn`/`targetOf`/`setClaimer` removed.

## What the owner cannot do

* Pause or limit withdrawals — no such code path exists (same as upstream).
* Upgrade or replace the contract — no proxy.
* Skim principal — the skim path is bounded by yield accounting.
* Sweep the asset, yield-vault shares, or vault shares.
* Set the yield fee above the upstream `MAX_YIELD_FEE` (90%).

## Key functions

```solidity
// ERC-4626 (1:1 on mint/deposit)
function deposit(uint256 assets, address receiver) external returns (uint256);
function withdraw(uint256 assets, address receiver, address owner) external returns (uint256);
function redeem(uint256 shares, address receiver, address owner) external returns (uint256);
function depositWithPermit(uint256 assets, address owner, uint256 deadline, uint8 v, bytes32 r, bytes32 s) external returns (uint256);

// Yield
function totalYieldBalance() external view returns (uint256);
function availableYieldBalance() external view returns (uint256);
function liquidatableBalanceOf(address tokenOut) external view returns (uint256); // 80% of available
function transferTokensOut(address, address receiver, address tokenOut, uint256 amountOut) external; // skimmer only
function claimYieldFeeShares(uint256 shares) external; // fee recipient only

// Stockpot deltas
function setDepositCap(uint256 cap) external; // owner
function sweep(address token, address recipient) external returns (uint256); // owner
```
