PegKeeper¶
crvUSD-style Curve StableSwap-NG peg defender. Mints/deposits TARE when pool price above $1; withdraws/burns when below.
Source: TARE-Stablecoin/src/peg_keeper.vy (Vyper 0.4.3)
Curve pool (Sepolia): 0xcBa8532fb8c16B7c193977e1A76E49fd06142d54 — address book
Concept guide: PegKeeper protocol page
Implementation overview¶
- Above peg — mint TARE (minter role), single-sided
add_liquidity→ increase pool supply, push price down. - Below peg —
remove_liquidity_one_coin+ burn TARE (up todebtpreviously provided) → reduce supply, lift price. - Partial correction — each
update()moves 1/5 of imbalance (CORRECTION_DIVISOR = 5). - Rate limit —
ACTION_DELAY15 min between state-changing ops. - Minter bound —
debt_ceiling+ engine grants minter viaset_peg_keeper.
flowchart LR
PK[PegKeeper] --> Pool[Curve NG pool]
PK -->|mint/burn| TARE[TareToken]
Engine[TareEngine] -->|set_peg_keeper| PK
Immutables¶
| Name | Role |
|---|---|
POOL |
Curve StableSwap-NG pool |
TARE / PEGGED |
TARE token |
I / I_OTHER |
TARE vs paired stable coin indices |
PAIRED_DECIMALS |
Paired stable decimals for balance normalization |
Events¶
| Event | When |
|---|---|
Provided |
TARE minted into pool |
Withdrawn |
TARE removed and burned |
DebtCeilingUpdated |
Admin changes ceiling |
SlippageSet |
Slippage tolerance updated |
AdminSet |
Admin transferred |
Admin¶
set_debt_ceiling¶
PegKeeper.set_debt_ceiling(new_ceiling)
Sets max TARE the keeper may have outstanding in the Curve pool (debt). Caps mint-and-provide peg defense.
| Param | Type | Description |
|---|---|---|
new_ceiling |
uint256 |
Max outstanding TARE debt (18 decimals) |
Returns / state: Writes debt_ceiling.
Access: admin only.
Events: DebtCeilingUpdated(ceiling=new_ceiling).
Source (TARE-Stablecoin/src/peg_keeper.vy:160-167):
@external
def set_debt_ceiling(new_ceiling: uint256):
assert msg.sender == self.admin, "PegKeeper: not admin"
self.debt_ceiling = new_ceiling
log DebtCeilingUpdated(ceiling=new_ceiling)
Example:
Cross-links: update · TareEngine set_peg_keeper
set_slippage_bps¶
PegKeeper.set_slippage_bps(new_bps)
Sets min-out slippage tolerance for pool provide/withdraw ops. Hard-capped at MAX_SLIPPAGE_BPS (1000 = 10%).
| Param | Type | Description |
|---|---|---|
new_bps |
uint256 |
Slippage tolerance in basis points |
Returns / state: Writes slippage_bps. Drives non-zero min_mint_amount / min_received on Curve calls.
Access: admin only. new_bps <= MAX_SLIPPAGE_BPS.
Events: SlippageSet(slippage_bps=new_bps).
Source (TARE-Stablecoin/src/peg_keeper.vy:170-182):
@external
def set_slippage_bps(new_bps: uint256):
assert msg.sender == self.admin, "PegKeeper: not admin"
assert new_bps <= MAX_SLIPPAGE_BPS, "PegKeeper: slippage too high"
self.slippage_bps = new_bps
log SlippageSet(slippage_bps=new_bps)
Example:
Cross-links: update · set_debt_ceiling
set_admin¶
PegKeeper.set_admin(new_admin)
Transfers keeper admin rights (debt ceiling, slippage config). Typically engine owner.
| Param | Type | Description |
|---|---|---|
new_admin |
address |
New admin address (non-zero) |
Returns / state: Writes admin.
Access: current admin only.
Events: AdminSet(admin=new_admin).
Source (TARE-Stablecoin/src/peg_keeper.vy:185-193):
@external
def set_admin(new_admin: address):
assert msg.sender == self.admin, "PegKeeper: not admin"
assert new_admin != empty(address), "PegKeeper: zero admin"
self.admin = new_admin
log AdminSet(admin=new_admin)
Cross-links: set_debt_ceiling · set_slippage_bps
Keeper¶
update¶
PegKeeper.update() -> uint256
Permissionless peg nudge. Corrects 1/5 of pool imbalance: mint+provide when TARE scarce (above peg), withdraw+burn when abundant (below peg). EMA price gate prevents flash-manipulation mints.
| Param | Type | Description |
|---|---|---|
| — | — | No inputs |
Returns: uint256 — TARE provided (mint direction) or withdrawn (burn direction); 0 if no action.
State: May update debt, last_change. Respects ACTION_DELAY (15 min), debt_ceiling, slippage_bps.
Access: anyone. Reverts if called too soon after last action.
Events: Provided or Withdrawn when change > 0.
Source (TARE-Stablecoin/src/peg_keeper.vy:200-243):
@external
def update() -> uint256:
assert block.timestamp >= self.last_change + ACTION_DELAY, "PegKeeper: too soon"
p_tare: uint256 = self._tare_price() # EMA guard
if balance_pegged > balance_other and p_tare <= PRECISION:
change = self._withdraw((balance_pegged - balance_other) // CORRECTION_DIVISOR)
elif balance_other > balance_pegged and p_tare >= PRECISION:
change = self._provide((balance_other - balance_pegged) // CORRECTION_DIVISOR)
if change > 0:
self.last_change = block.timestamp
return change
Example:
Cross-links: tare_price · peg_deviation_bps · estimate_update · TareToken API
Views¶
tare_price¶
PegKeeper.tare_price() -> uint256
TARE's Curve pool EMA price in paired-stable terms (18-decimal; 1e18 = $1 target).
| Param | Type | Description |
|---|---|---|
| — | — | No inputs |
Returns: uint256 — USD value of 1 TARE from POOL.price_oracle(0), oriented via coin index I.
State: None (view). Uses manipulation-resistant EMA, not spot balances.
Access: anyone (@view).
Events: None.
Source (TARE-Stablecoin/src/peg_keeper.vy:322-338):
@external
@view
def tare_price() -> uint256:
raw: uint256 = staticcall POOL.price_oracle(0)
if I == 1:
return raw
return PRECISION * PRECISION // raw
Example:
Cross-links: peg_deviation_bps · update · estimate_update
peg_deviation_bps¶
PegKeeper.peg_deviation_bps() -> uint256
Absolute TARE peg deviation from $1 in basis points (pool EMA).
| Param | Type | Description |
|---|---|---|
| — | — | No inputs |
Returns: uint256 — |price - 1e18| / 1e18 × 10_000 bps.
State: None (view). Uses _tare_price() internally.
Access: anyone (@view).
Events: None.
Source (TARE-Stablecoin/src/peg_keeper.vy:341-348):
@external
@view
def peg_deviation_bps() -> uint256:
price: uint256 = self._tare_price()
if price >= PRECISION:
return (price - PRECISION) * 10_000 // PRECISION
return (PRECISION - price) * 10_000 // PRECISION
Example:
Cross-links: tare_price · update
estimate_update¶
PegKeeper.estimate_update() -> (bool, uint256)
Preview next update() direction and size from pool balances. Does not apply EMA gate or ACTION_DELAY — dry-run sizing only.
| Param | Type | Description |
|---|---|---|
| — | — | No inputs |
Returns: (is_provide, amount) — True = mint/provide path; False = withdraw/burn. amount = TARE that would move (withdraw capped at debt).
State: None (view).
Access: anyone (@view).
Events: None.
Source (TARE-Stablecoin/src/peg_keeper.vy:360-378):
@external
@view
def estimate_update() -> (bool, uint256):
if balance_pegged > balance_other:
return (False, min((balance_pegged - balance_other) // CORRECTION_DIVISOR, self.debt))
if balance_other > balance_pegged:
return (True, (balance_other - balance_pegged) // CORRECTION_DIVISOR)
return (True, 0)
Example:
Cross-links: update · peg_deviation_bps