Tranche¶
ERC-4626 share token for one side of a TranchedVault waterfall — senior (skUSD) or junior (jkUSD). Same contract for both roles; is_senior immutable picks which vault hook runs on deposit/exit.
Source: khomdev-keep/src/Tranche.vy (Vyper 0.4.x)
Concept guide: Tranches protocol page
Pairs with: TranchedVault · underlying USDC via vault
Implementation overview¶
- NAV —
totalAssets()readssenior_valueorjunior_valuefrom TranchedVault. - Share math — ERC-4626 with
DECIMALS_OFFSET = 6virtual shares (OpenZeppelin-style inflation guard). - Deposits — pull USDC → approve TV →
deposit_senior/deposit_junior; mint tranche shares. - Exits — burn shares →
process_senior_exit/process_junior_exit; penalty handled in TV. - Impairment guard — if
totalSupply > 0and tranche value< impaired_floor, new deposits blocked (prevents re-subscription dilution after wipe). - Penalty semantics —
withdrawburns shares for grossassets; receiver gets net after TV penalty.redeemreturns net paid.preview*exclude penalty (standard “if-no-fees”).
flowchart LR
U[User USDC] --> T[Tranche ERC-4626]
T --> TV[TranchedVault]
TV --> MSV[MultiStrategyVault]
Immutables¶
| Name | Role |
|---|---|
asset |
Underlying USDC |
tranched_vault |
Parent TranchedVault |
is_senior |
True → skUSD hooks; False → jkUSD |
impaired_floor |
Min tranche value (1 whole token) before deposits reopen after impairment |
ERC-20 surface¶
Standard ERC-20 + EIP-2612 permit re-exported from snekmate erc20 (transfer, approve, permit, etc.). Not duplicated here.
Events¶
| Event | When |
|---|---|
Deposit |
Shares minted on deposit/mint |
Withdraw |
Shares burned on withdraw/redeem (logs net assets paid) |
ERC-4626 views¶
totalAssets¶
Tranche.totalAssets() -> uint256
Live tranche NAV in underlying USDC — not token balance held by this contract.
Returns: uint256 — senior_value() or junior_value() on parent TranchedVault, per is_senior.
Access: view, any caller.
Source (khomdev-keep/src/Tranche.vy:149-153, _total_assets 297-300):
@external
@view
def totalAssets() -> uint256:
return self._total_assets()
# internal:
if is_senior:
return ITranchedVault(tranched_vault).senior_value()
return ITranchedVault(tranched_vault).junior_value()
Example:
Cross-links: convertToShares · TranchedVault senior_value · TranchedVault junior_value
convertToShares¶
Tranche.convertToShares(assets_in) -> uint256
ERC-4626 quote: underlying USDC → tranche shares (floor rounding).
| Param | Type | Description |
|---|---|---|
assets_in |
uint256 |
USDC amount (6 decimals) |
Returns: uint256 — share amount. Uses virtual shares (DECIMALS_OFFSET = 6) against live totalAssets.
Access: view, any caller.
Source (khomdev-keep/src/Tranche.vy:158-162, _convert_to_shares 304-310):
@external
@view
def convertToShares(assets_in: uint256) -> uint256:
return self._convert_to_shares(assets_in, False)
# shares = assets * (totalSupply + 10**6) / (totalAssets + 1)
Example:
Cross-links: convertToAssets · totalAssets · previewDeposit
convertToAssets¶
Tranche.convertToAssets(shares) -> uint256
ERC-4626 quote: tranche shares → underlying USDC (floor rounding).
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Tranche share amount |
Returns: uint256 — USDC value at current totalAssets / totalSupply. Excludes early-exit penalty (standard if-no-fees quote).
Access: view, any caller.
Source (khomdev-keep/src/Tranche.vy:167-171, _convert_to_assets 314-320):
@external
@view
def convertToAssets(shares: uint256) -> uint256:
return self._convert_to_assets(shares, False)
# assets = shares * (totalAssets + 1) / (totalSupply + 10**6)
Example:
Cross-links: convertToShares · totalAssets · previewRedeem
maxDeposit¶
Tranche.maxDeposit(receiver) -> uint256
ERC-4626 deposit cap query.
| Param | Type | Description |
|---|---|---|
receiver |
address |
Share recipient (unused in limit math; ERC-4626 signature) |
Returns: uint256 — always max_uint256. No on-chain deposit cap in this view.
Access: view, any caller.
Note: deposit can still revert if tranche is impaired (totalAssets < impaired_floor with totalSupply > 0) or TranchedVault is paused.
Source (khomdev-keep/src/Tranche.vy:176-180):
Example:
Cross-links: maxMint · deposit · previewDeposit
maxMint¶
Tranche.maxMint(receiver) -> uint256
ERC-4626 mint cap query.
| Param | Type | Description |
|---|---|---|
receiver |
address |
Share recipient (ERC-4626 signature) |
Returns: uint256 — always max_uint256. Same practical limits as maxDeposit.
Access: view, any caller.
Source (khomdev-keep/src/Tranche.vy:185-189):
Example:
Cross-links: maxDeposit · mint · previewMint
maxWithdraw¶
Tranche.maxWithdraw(owner) -> uint256
Largest gross USDC withdraw amount for owner at current NAV.
| Param | Type | Description |
|---|---|---|
owner |
address |
Share holder |
Returns: uint256 — convertToAssets(balanceOf(owner)). Full position in underlying terms (gross; penalty applied on execution).
Access: view, any caller.
Note: Does not cap for MSV liquidity — exit may revert at TranchedVault if MSV cannot fund process_*_exit.
Source (khomdev-keep/src/Tranche.vy:194-198):
@external
@view
def maxWithdraw(owner: address) -> uint256:
return self._convert_to_assets(erc20.balanceOf[owner], False)
Example:
Cross-links: maxRedeem · withdraw · convertToAssets
maxRedeem¶
Tranche.maxRedeem(owner) -> uint256
Largest share count redeemable for owner.
| Param | Type | Description |
|---|---|---|
owner |
address |
Share holder |
Returns: uint256 — full balanceOf(owner). No liquidity haircut in this view.
Access: view, any caller.
Note: redeem returns net paid (post-penalty). MSV funding limits apply at TranchedVault exit, not reflected here.
Source (khomdev-keep/src/Tranche.vy:203-207):
Example:
Cross-links: maxWithdraw · redeem · previewRedeem
previewDeposit¶
Tranche.previewDeposit(assets_in) -> uint256
Simulate deposit — shares minted for given USDC (floor rounding).
| Param | Type | Description |
|---|---|---|
assets_in |
uint256 |
USDC to deposit |
Returns: uint256 — expected shares (= convertToShares at current rate).
Access: view, any caller. Does not check impairment or TV pause.
Source (khomdev-keep/src/Tranche.vy:212-216):
@external
@view
def previewDeposit(assets_in: uint256) -> uint256:
return self._convert_to_shares(assets_in, False)
Example:
Cross-links: deposit · convertToShares · maxDeposit
previewMint¶
Tranche.previewMint(shares) -> uint256
Simulate mint — USDC required for exact share amount (ceil rounding).
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Target share amount |
Returns: uint256 — USDC cost (= _convert_to_assets(shares, roundup=True)).
Access: view, any caller. Does not check impairment or TV pause.
Source (khomdev-keep/src/Tranche.vy:221-225):
@external
@view
def previewMint(shares: uint256) -> uint256:
return self._convert_to_assets(shares, True)
Example:
Cross-links: mint · convertToAssets · maxMint
previewWithdraw¶
Tranche.previewWithdraw(assets_in) -> uint256
Simulate withdraw — shares burned for exact gross USDC position (ceil rounding).
| Param | Type | Description |
|---|---|---|
assets_in |
uint256 |
Gross USDC position to exit |
Returns: uint256 — shares required (= _convert_to_shares(assets_in, roundup=True)). Excludes early-exit penalty; receiver gets less pre-maturity on actual withdraw.
Access: view, any caller.
Source (khomdev-keep/src/Tranche.vy:230-234):
@external
@view
def previewWithdraw(assets_in: uint256) -> uint256:
return self._convert_to_shares(assets_in, True)
Example:
Cross-links: withdraw · convertToShares · maxWithdraw
previewRedeem¶
Tranche.previewRedeem(shares) -> uint256
Simulate redeem — gross USDC for burning shares (floor rounding).
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Shares to redeem |
Returns: uint256 — gross position value (= convertToAssets). Excludes early-exit penalty; actual redeem returns net paid.
Access: view, any caller.
Source (khomdev-keep/src/Tranche.vy:239-243):
@external
@view
def previewRedeem(shares: uint256) -> uint256:
return self._convert_to_assets(shares, False)
Example:
Cross-links: redeem · convertToAssets · maxRedeem
ERC-4626 writes¶
deposit¶
Tranche.deposit(assets_in, receiver) -> uint256
Deposit USDC, mint tranche shares to receiver. Routes into TranchedVault via deposit_senior / deposit_junior.
| Param | Type | Description |
|---|---|---|
assets_in |
uint256 |
USDC amount |
receiver |
address |
Share recipient |
Returns: uint256 — shares minted.
Returns / state: Pulls USDC from msg.sender → approves TV → vault deposit → mints shares. Reverts if impaired or TV paused.
Access: Any caller. nonreentrant.
Events: Deposit(sender, owner, assets, shares).
Source (khomdev-keep/src/Tranche.vy:251-260):
@external
@nonreentrant
def deposit(assets_in: uint256, receiver: address) -> uint256:
shares = self._convert_to_shares(assets_in, False)
assert shares != 0, "tranche: zero shares"
self._do_deposit(msg.sender, receiver, assets_in, shares)
return shares
Example:
Cross-links: previewDeposit · TranchedVault deposit_senior · TranchedVault deposit_junior
mint¶
Tranche.mint(shares, receiver) -> uint256
Mint exact shares to receiver; pulls ceil-rounded USDC from msg.sender.
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Target share amount |
receiver |
address |
Share recipient |
Returns: uint256 — USDC pulled from msg.sender.
Returns / state: Same deposit path as deposit via _do_deposit.
Access: Any caller. nonreentrant.
Events: Deposit(sender, owner, assets, shares).
Source (khomdev-keep/src/Tranche.vy:264-268):
@external
@nonreentrant
def mint(shares: uint256, receiver: address) -> uint256:
assets_in = self._convert_to_assets(shares, True)
assert assets_in != 0, "tranche: zero assets"
self._do_deposit(msg.sender, receiver, assets_in, shares)
return assets_in
Example:
Cross-links: previewMint · deposit
withdraw¶
Tranche.withdraw(assets_in, receiver, owner) -> uint256
Burn shares for gross USDC position; pay net to receiver after TV early-exit penalty.
| Param | Type | Description |
|---|---|---|
assets_in |
uint256 |
Gross USDC position to exit |
receiver |
address |
USDC payout recipient |
owner |
address |
Share holder (caller or approved) |
Returns: uint256 — shares burned (ceil vs gross assets_in).
Returns / state: Burns shares → TranchedVault process_senior_exit / process_junior_exit. receiver gets net paid; penalty stays in MSV.
Access: Any caller with share allowance on owner. Not blocked by TV pause.
Events: Withdraw(sender, receiver, owner, assets, shares) — assets is net paid.
Source (khomdev-keep/src/Tranche.vy:272-280):
@external
@nonreentrant
def withdraw(assets_in: uint256, receiver: address, owner: address) -> uint256:
shares = self._convert_to_shares(assets_in, True)
self._do_exit(msg.sender, receiver, owner, shares)
return shares
Example:
Cross-links: previewWithdraw · TranchedVault process_senior_exit · redeem
redeem¶
Tranche.redeem(shares, receiver, owner) -> uint256
Burn exact shares; pay net USDC to receiver (post-penalty).
| Param | Type | Description |
|---|---|---|
shares |
uint256 |
Shares to burn |
receiver |
address |
USDC recipient |
owner |
address |
Share holder |
Returns: uint256 — net USDC paid to receiver.
Returns / state: Same exit path as withdraw via _do_exit.
Access: Any caller with share allowance. Not blocked by TV pause.
Events: Withdraw(sender, receiver, owner, assets, shares).
Source (khomdev-keep/src/Tranche.vy:284-289):
@external
@nonreentrant
def redeem(shares: uint256, receiver: address, owner: address) -> uint256:
return self._do_exit(msg.sender, receiver, owner, shares)
Example:
Cross-links: previewRedeem · withdraw · TranchedVault process_junior_exit