Monetary Policy
MonetaryPolicy contracts are integrated into the crvUSD ecosystem, where they play a pivotal role in determining the interest rates for crvUSD markets.
AggMonetaryPolicy.vyThe source code for the AggMonetaryPolicy.vy contract can be found on GitHub. The contract is written in Vyper version 0.3.7.
The contract is deployed on Ethereum at
0xc684432fd6322c6d58b6bc5d28b18569aa0ad0a1.
{ }Contract ABI▼
[{"name": "SetAdmin", "inputs": [{"name": "admin", "type": "address", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "AddPegKeeper", "inputs": [{"name": "peg_keeper", "type": "address", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "RemovePegKeeper", "inputs": [{"name": "peg_keeper", "type": "address", "indexed": true}], "anonymous": false, "type": "event"}, {"name": "SetRate", "inputs": [{"name": "rate", "type": "uint256", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "SetSigma", "inputs": [{"name": "sigma", "type": "uint256", "indexed": false}], "anonymous": false, "type": "event"}, {"name": "SetTargetDebtFraction", "inputs": [{"name": "target_debt_fraction", "type": "uint256", "indexed": false}], "anonymous": false, "type": "event"}, {"stateMutability": "nonpayable", "type": "constructor", "inputs": [{"name": "admin", "type": "address"}, {"name": "price_oracle", "type": "address"}, {"name": "controller_factory", "type": "address"}, {"name": "peg_keepers", "type": "address[5]"}, {"name": "rate", "type": "uint256"}, {"name": "sigma", "type": "uint256"}, {"name": "target_debt_fraction", "type": "uint256"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "set_admin", "inputs": [{"name": "admin", "type": "address"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "add_peg_keeper", "inputs": [{"name": "pk", "type": "address"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "remove_peg_keeper", "inputs": [{"name": "pk", "type": "address"}], "outputs": []}, {"stateMutability": "view", "type": "function", "name": "rate", "inputs": [], "outputs": [{"name": "", "type": "uint256"}]}, {"stateMutability": "nonpayable", "type": "function", "name": "rate_write", "inputs": [], "outputs": [{"name": "", "type": "uint256"}]}, {"stateMutability": "nonpayable", "type": "function", "name": "set_rate", "inputs": [{"name": "rate", "type": "uint256"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "set_sigma", "inputs": [{"name": "sigma", "type": "uint256"}], "outputs": []}, {"stateMutability": "nonpayable", "type": "function", "name": "set_target_debt_fraction", "inputs": [{"name": "target_debt_fraction", "type": "uint256"}], "outputs": []}, {"stateMutability": "view", "type": "function", "name": "admin", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"stateMutability": "view", "type": "function", "name": "rate0", "inputs": [], "outputs": [{"name": "", "type": "uint256"}]}, {"stateMutability": "view", "type": "function", "name": "sigma", "inputs": [], "outputs": [{"name": "", "type": "int256"}]}, {"stateMutability": "view", "type": "function", "name": "target_debt_fraction", "inputs": [], "outputs": [{"name": "", "type": "uint256"}]}, {"stateMutability": "view", "type": "function", "name": "peg_keepers", "inputs": [{"name": "arg0", "type": "uint256"}], "outputs": [{"name": "", "type": "address"}]}, {"stateMutability": "view", "type": "function", "name": "PRICE_ORACLE", "inputs": [], "outputs": [{"name": "", "type": "address"}]}, {"stateMutability": "view", "type": "function", "name": "CONTROLLER_FACTORY", "inputs": [], "outputs": [{"name": "", "type": "address"}]}]
Interest Rate Mechanics
The interest rates in crvUSD markets are not static but fluctuate based on a set of factors, including:
- The price of crvUSD, which is determined through an aggregated oracle price from multiple Curve Stableswap pools (details here).
- The variables
sigma,rate0,TargetFraction, and theDebtFractionspecific to PegKeepers.
A useful tool to explore and understand how the rate is affected by 0xreviews is avaliable at: https://crvusd-rate.0xreviews.xyz/
The formula for calculating the interest rate (r) is as follows:
Where:
And the DebtFraction is defined by:
Key variables in this calculation include:
r: the interest raterate0: the baseline rate, applicable when PegKeepers are debt-free and the crvUSD price equals 1price_peg: the target crvUSD price, set at 1.00price_crvusd: the actual crvUSD price, aggregated fromPRICE_ORACLE.price()DebtFraction: the portion of PegKeeper debt relative to the total outstanding debtTargetFraction: the designated target fractionPegKeeperDebt: the cumulative debt of all PegKeepersTotalDebt: the aggregate crvUSD debt
For accuracy and consistency, both rate and rate0 are expressed in terms of to denote precision and are calculated per second.
The annualized interest rate can be computed as:
rate
MonetaryPolicy.rate() -> uint256: viewGetter for the rate of the monetary policy contract. This is the current interest rate paid per second.
Returns: rate (uint256).
<>Source code▼
@view
@external
def rate() -> uint256:
return self.calculate_rate()
@internal
@view
def calculate_rate() -> uint256:
sigma: int256 = self.sigma
target_debt_fraction: uint256 = self.target_debt_fraction
p: int256 = convert(PRICE_ORACLE.price(), int256)
pk_debt: uint256 = 0
for pk in self.peg_keepers:
if pk.address == empty(address):
break
pk_debt += pk.debt()
power: int256 = (10**18 - p) * 10**18 / sigma # high price -> negative pow -> low rate
if pk_debt > 0:
total_debt: uint256 = CONTROLLER_FACTORY.total_debt()
if total_debt == 0:
return 0
else:
power -= convert(pk_debt * 10**18 / total_debt * 10**18 / target_debt_fraction, int256)
return self.rate0 * min(self.exp(power), MAX_EXP) / 10**18
▶Example▼
rate0
MonetaryPolicy.rate0() -> uint256: viewGetter for the rate0 of the monetary policy contract. rate0 has to be less than or equal to MAX_RATE (400% APY).
Returns: rate0 (uint256).
<>Source code▼
MAX_RATE: constant(uint256) = 43959106799 # 400% APY
rate0: public(uint256)
@external
def __init__(admin: address,
price_oracle: PriceOracle,
controller_factory: ControllerFactory,
peg_keepers: PegKeeper[5],
rate: uint256,
sigma: uint256,
target_debt_fraction: uint256):
...
assert rate <= MAX_RATE
self.rate0 = rate
...
▶Example▼
set_rate
MonetaryPolicy.set_rate(rate: uint256)This function is only callable by the admin of the contract, which is the CurveOwnershipAgent.
Function to set a new rate0. New rate0 has to be less than or equal to MAX_RATE (=43959106799).
| Input | Type | Description |
|---|---|---|
rate | uint256 | New rate0 value |
Emits: SetRate event.
<>Source code▼
event SetRate:
rate: uint256
MAX_RATE: constant(uint256) = 43959106799 # 400% APY
rate0: public(uint256)
@external
def set_rate(rate: uint256):
assert msg.sender == self.admin
assert rate <= MAX_RATE
self.rate0 = rate
log SetRate(rate)
▶Example▼
>>> MonetaryPolicy.set_rate(3488077118)
sigma
MonetaryPolicy.sigma() -> int256: viewGetter for the sigma value. The following needs to hold: .
Returns: sigma (int256).
<>Source code▼
sigma: public(int256) # 2 * 10**16 for example
MAX_SIGMA: constant(uint256) = 10**18
MIN_SIGMA: constant(uint256) = 10**14
@external
def __init__(admin: address,
price_oracle: PriceOracle,
controller_factory: ControllerFactory,
peg_keepers: PegKeeper[5],
rate: uint256,
sigma: uint256,
target_debt_fraction: uint256):
...
assert sigma >= MIN_SIGMA
assert sigma <= MAX_SIGMA
...
▶Example▼
set_sigma
MonetaryPolicy.set_sigma(sigma: uint256)This function is only callable by the admin of the contract, which is the CurveOwnershipAgent.
Function to set a new sigma value. New value must be inbetween MIN_SIGMA and MAX_SIGMA.
| Input | Type | Description |
|---|---|---|
sigma | uint256 | New sigma value |
Emits: SetSigma event.
<>Source code▼
event SetSigma:
sigma: uint256
sigma: public(int256) # 2 * 10**16 for example
MAX_SIGMA: constant(uint256) = 10**18
MIN_SIGMA: constant(uint256) = 10**14
@external
def set_sigma(sigma: uint256):
assert msg.sender == self.admin
assert sigma >= MIN_SIGMA
assert sigma <= MAX_SIGMA
self.sigma = convert(sigma, int256)
log SetSigma(sigma)
▶Example▼
>>> MonetaryPolicy.set_sigma(30000000000000000)
target_debt_fraction
MonetaryPolicy.target_debt_fraction() -> uint256: viewGetter for the debt fraction target.
Returns: target debt fraction (uint256).
<>Source code▼
MAX_TARGET_DEBT_FRACTION: constant(uint256) = 10**18
target_debt_fraction: public(uint256)
@external
def __init__(admin: address,
price_oracle: PriceOracle,
controller_factory: ControllerFactory,
peg_keepers: PegKeeper[5],
rate: uint256,
sigma: uint256,
target_debt_fraction: uint256):
...
self.target_debt_fraction = target_debt_fraction
▶Example▼
set_target_debt_fraction
MonetaryPolicy.set_target_debt_fraction(target_debt_fraction: uint256)This function is only callable by the admin of the contract, which is the CurveOwnershipAgent.
Function to set a new value for the debt fraction target. New value needs to be less than or equal to MAX_TARGET_DEBT_FRACTION.
| Input | Type | Description |
|---|---|---|
target_debt_fraction | uint256 | New debt fraction target value |
Emits: SetTargetDebtFraction event.
<>Source code▼
event SetTargetDebtFraction:
target_debt_fraction: uint256
MAX_TARGET_DEBT_FRACTION: constant(uint256) = 10**18
target_debt_fraction: public(uint256)
@external
def set_target_debt_fraction(target_debt_fraction: uint256):
assert msg.sender == self.admin
assert target_debt_fraction <= MAX_TARGET_DEBT_FRACTION
self.target_debt_fraction = target_debt_fraction
log SetTargetDebtFraction(target_debt_fraction)
▶Example▼
>>> MonetaryPolicy.set_target_debt_fraction(200000000000000000)
PegKeepers
PegKeepers must be added to the MonetaryPolicy contract to calculate the rate as it depends on the DebtFraction. They can be added by calling add_peg_keeper and removed via remove_peg_keeper.
peg_keepers
MonetaryPolicy.peg_keepers(arg0: uint256) -> address: viewGetter for the PegKeeper contract at index arg0.
| Input | Type | Description |
|---|---|---|
arg0 | uint256 | Index of the PegKeeper |
Returns: PegKeeper contracts (address).
<>Source code▼
interface PegKeeper:
def debt() -> uint256: view
peg_keepers: public(PegKeeper[1001])
@external
def __init__(admin: address,
price_oracle: PriceOracle,
controller_factory: ControllerFactory,
peg_keepers: PegKeeper[5],
rate: uint256,
sigma: uint256,
target_debt_fraction: uint256):
...
for i in range(5):
if peg_keepers[i].address == empty(address):
break
self.peg_keepers[i] = peg_keepers[i]
...
▶Example▼
add_peg_keeper
MonetaryPolicy.add_peg_keeper(pk: PegKeeper)This function is only callable by the admin of the contract.
Function to add an existing PegKeeper to the monetary policy contract.
| Input | Type | Description |
|---|---|---|
pk | PegKeeper | PegKeeper address to add |
Emits: AddPegKeeper event.
<>Source code▼
event AddPegKeeper:
peg_keeper: indexed(address)
peg_keepers: public(PegKeeper[1001])
@external
def add_peg_keeper(pk: PegKeeper):
assert msg.sender == self.admin
assert pk.address != empty(address)
for i in range(1000):
_pk: PegKeeper = self.peg_keepers[i]
assert _pk != pk, "Already added"
if _pk.address == empty(address):
self.peg_keepers[i] = pk
log AddPegKeeper(pk.address)
break
▶Example▼
>>> MonetaryPolicy.add_peg_keeper("PegKeeper address")
remove_peg_keeper
MonetaryPolicy.remove_peg_keeper(pk: PegKeeper)This function is only callable by the admin of the contract.
Function to remove an existing PegKeeper from the monetary policy contract.
| Input | Type | Description |
|---|---|---|
pk | PegKeeper | PegKeeper address to remove |
Emits: RemovePegKeeper event.
<>Source code▼
event RemovePegKeeper:
peg_keeper: indexed(address)
peg_keepers: public(PegKeeper[1001])
@external
def remove_peg_keeper(pk: PegKeeper):
assert msg.sender == self.admin
replaced_peg_keeper: uint256 = 10000
for i in range(1001): # 1001th element is always 0x0
_pk: PegKeeper = self.peg_keepers[i]
if _pk == pk:
replaced_peg_keeper = i
log RemovePegKeeper(pk.address)
if _pk.address == empty(address):
if replaced_peg_keeper < i:
if replaced_peg_keeper < i - 1:
self.peg_keepers[replaced_peg_keeper] = self.peg_keepers[i - 1]
self.peg_keepers[i - 1] = PegKeeper(empty(address))
break
▶Example▼
>>> MonetaryPolicy.remove_peg_keeper("PegKeeper address")
Admin Ownership
admin
MonetaryPolicy.admin() -> address: viewGetter for the admin of the contract, which is the CurveOwnershipAgent.
Returns: admin (address).
<>Source code▼
admin: public(address)
@external
def __init__(admin: address,
price_oracle: PriceOracle,
controller_factory: ControllerFactory,
peg_keepers: PegKeeper[5],
rate: uint256,
sigma: uint256,
target_debt_fraction: uint256):
self.admin = admin
...
▶Example▼
set_admin
MonetaryPolicy.set_admin(admin: address)This function is only callable by the admin of the contract, which is the CurveOwnershipAgent.
Function to set a new admin.
| Input | Type | Description |
|---|---|---|
admin | address | New admin address |
Emits: SetAdmin event.
<>Source code▼
event SetAdmin:
admin: address
admin: public(address)
@external
def set_admin(admin: address):
assert msg.sender == self.admin
self.admin = admin
log SetAdmin(admin)
▶Example▼
>>> MonetaryPolicy.set_admin("0xd8dA6BF26964aF9D7eEd9e03E53415D37aA96045")
Contract Info Methods
PRICE_ORACLE
MonetaryPolicy.PRICE_ORACLE() -> address: viewGetter for the price oracle contract.
Returns: price oracle contract (address).
<>Source code▼
PRICE_ORACLE: public(immutable(PriceOracle))
@external
def __init__(admin: address,
price_oracle: PriceOracle,
controller_factory: ControllerFactory,
peg_keepers: PegKeeper[5],
rate: uint256,
sigma: uint256,
target_debt_fraction: uint256):
...
PRICE_ORACLE = price_oracle
...
▶Example▼
CONTROLLER_FACTORY
MonetaryPolicy.CONTROLLER_FACTORY() -> address: viewGetter for the controller factory contract. immutable variable!
Returns: controller factory contract (address).
<>Source code▼
CONTROLLER_FACTORY: public(immutable(ControllerFactory))
@external
def __init__(admin: address,
price_oracle: PriceOracle,
controller_factory: ControllerFactory,
peg_keepers: PegKeeper[5],
rate: uint256,
sigma: uint256,
target_debt_fraction: uint256):
...
CONTROLLER_FACTORY = controller_factory
...
▶Example▼
rate_write
MonetaryPolicy.rate_write() -> uint256When adding a new market via the factory contract, rate_write is called to check if the MonetaryPolicy contract has the correct ABI.
Returns: the current rate (uint256).
<>Source code▼
@external
def rate_write() -> uint256:
# Not needed here but useful for more automated policies
# which change rate0 - for example rate0 targeting some fraction pl_debt/total_debt
return self.calculate_rate()
@internal
@view
def calculate_rate() -> uint256:
sigma: int256 = self.sigma
target_debt_fraction: uint256 = self.target_debt_fraction
p: int256 = convert(PRICE_ORACLE.price(), int256)
pk_debt: uint256 = 0
for pk in self.peg_keepers:
if pk.address == empty(address):
break
pk_debt += pk.debt()
power: int256 = (10**18 - p) * 10**18 / sigma # high price -> negative pow -> low rate
if pk_debt > 0:
total_debt: uint256 = CONTROLLER_FACTORY.total_debt()
if total_debt == 0:
return 0
else:
power -= convert(pk_debt * 10**18 / total_debt * 10**18 / target_debt_fraction, int256)
return self.rate0 * min(self.exp(power), MAX_EXP) / 10**18
▶Example▼
>>> MonetaryPolicy.rate_write()
3488503937