MonetaryPolicy
MonetaryPolicy contracts are integrated into the crvUSD system and are responsible for the interest rate of crvUSD markets.
Contract Source & Deployment
Source code available on Github.
MonetaryPolicy | Deployment Address |
---|---|
wstETH, sfrxETH2 | 0x1e7d3bf98d3f8d8ce193236c3e0ec4b00e32daae |
sfrxETH | 0xc684432fd6322c6d58b6bc5d28b18569aa0ad0a1 |
ETH | 0xBB3fda661149f6E45D829D5dd54a1608577c5Fa1 |
wBTC, tBTC | 0xb8687d7dc9d8fa32fabde63E19b2dBC9bB8B2138 |
Interest Rate¶
Markets have a dynamic rate, depending on the following components:
- crvUSD price
- sigma
- rate0
- TargetFraction
- DebtFraction of PegKeepers
For the price of crvUSD, an aggregated oracle price of multiple Curve Stablwswap pools is used (see here).
Tip
Useful tool by 0xreviews to play around with rates: https://crvusd-rate.0xreviews.xyz/
variable | description |
---|---|
r | rate |
rate0 | rate when pegkeepers have no debt and price of crvusd is 1 |
price_peg | desired crvUSD price: 1.00 |
price_crvusd | actual crvUSD price (aggregated from PRICE_ORACLE.price() ) |
DebtFraction | ratio of the PegKeeper's debt to the total outstanding debt |
TargetFraction | target fraction |
PegKeeperDebt | sum of debt of all PegKeepers |
TotalDebt | total crvUSD debt |
Tip
rate
and rate0
denominated in units of \(10^{18}\) for precision and represent the rate per second.
\(\text{annualRate} = (1 + \frac{rate}{10^{18}})^{365*24*60*60} - 1\)
The code examples below are based on the 0x8c...c933 MonetaryPolicy.
rate
¶
MonetaryPolicy.rate() -> uint256: view
Getter 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
rate0
¶
MonetaryPolicy.rate0() -> uint256: view
Getter 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
...
set_rate
¶
MonetaryPolicy.set_rate(rate: uint256):
Guarded Method
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)
.
Emits: SetRate
Input | Type | Description |
---|---|---|
rate | uint256 | New rate0 value |
Source code
sigma
¶
MonetaryPolicy.sigma() -> uint256: view
Getter for the sigma value. The following needs to hold: \(10^{14} <= sigma <= 10^{18}\).
Returns: sigma (uint256
).
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
...
set_sigma
¶
MonetaryPolicy.set_sigma(sigma: uint256):
Guarded Method
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
.
Emits: SetSigma
Input | Type | Description |
---|---|---|
sigma | uint256 | New sigma value |
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)
target_debt_fraction
¶
MonetaryPolicy.target_debt_fraction() -> uint256: view
Getter 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
set_target_debt_fraction
¶
MonetaryPolicy.set_target_debt_fraction(target_debt_fraction: uint256):
Guarded Method
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
.
Emits: SetTargetDebtFraction
Input | Type | Description |
---|---|---|
target_debt_fraction | uint256 | New debt fraction target value |
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)
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: view
Getter for the PegKeeper contract at index arg0
.
Returns: PegKeeper contracts (address
).
Input | Type | Description |
---|---|---|
arg0 | uint256 | Index of the PegKeeper |
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]
...
add_peg_keeper
¶
MonetaryPolicy.add_peg_keeper(pk: PegKeeper):
Guarded Method
This function is only callable by the admin
of the contract.
Function to add an existing PegKeeper to the monetary policy contract.
Emits: AddPegKeeper
Input | Type | Description |
---|---|---|
pk | PegKeeper | PegKeeper address to add |
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
remove_peg_keeper
¶
MonetaryPolicy.remove_peg_keeper(pk: PegKeeper):
Guarded Method
This function is only callable by the admin
of the contract.
Function to remove an existing PegKeeper from the monetary policy contract.
Emits: RemovePegKeeper
Input | Type | Description |
---|---|---|
pk | PegKeeper | PegKeeper address to remove |
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
Admin Ownership¶
admin
¶
MonetaryPolicy.admin() -> address: view
Getter for the admin of the contract, which is the CurveOwnershipAgent.
Returns: admin (address
).
Source code
set_admin
¶
MonetaryPolicy.set_admin(admin: address):
Guarded Method
This function is only callable by the admin
of the contract, which is the CurveOwnershipAgent.
Function to set a new admin.
Emits: SetAdmin
Input | Type | Description |
---|---|---|
admin | address | New admin address |
Source code
Contract Info Methods¶
PRICE_ORACLE
¶
MonetaryPolicy.PRICE_ORACLE() -> address: view
Getter for the price oracle contract.
Returns: price oracle contract (address
).
Source code
CONTROLLER_FACOTRY
¶
MonetaryPolicy.CONTROLLER_FACOTRY() -> address: view
Getter for the controller factory contract. immutable variable!
Returns: controller factory contract (address
).
Source code
rate_write
¶
MonetaryPolicy.rate_write() -> uint256:
When adding a new market via the factory contract, rate_write
is called to check if the MonetaryPolicy contract has the correct ABI.
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