# @version 0.3.10 """ @title SemiLog monetary policy @notice Monetary policy to calculate borrow rates in lending markets depending on utilization. Unlike "core" policies, it does not depend on crvUSD price. Calculated as: log(rate) = utilization * (log(rate_max) - log(rate_min)) + log(rate_min) e.g. rate = rate_min * (rate_max / rate_min)**utilization @author Curve.fi @license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved """ # TODO bump to vyper 0.4.3 from vyper.interfaces import ERC20 interface Controller: def total_debt() -> uint256: view interface Factory: def admin() -> address: view event SetRates: min_rate: uint256 max_rate: uint256 MAX_EXP: constant(uint256) = 1000 * 10**18 MIN_RATE: public(constant(uint256)) = 10**15 / (365 * 86400) # 0.1% MAX_RATE: public(constant(uint256)) = 10**19 / (365 * 86400) # 1000% BORROWED_TOKEN: public(immutable(ERC20)) FACTORY: public(immutable(Factory)) min_rate: public(uint256) max_rate: public(uint256) log_min_rate: public(int256) log_max_rate: public(int256) @external def __init__( borrowed_token: ERC20, min_rate: uint256, max_rate: uint256, _factory: address ): assert min_rate >= MIN_RATE and max_rate <= MAX_RATE and min_rate <= max_rate, "Wrong rates" BORROWED_TOKEN = borrowed_token self.min_rate = min_rate self.max_rate = max_rate self.log_min_rate = self.ln_int(min_rate) self.log_max_rate = self.ln_int(max_rate) FACTORY = Factory(_factory) ### MATH ### @internal @pure def exp(power: int256) -> uint256: if power <= -41446531673892821376: return 0 if power >= 135305999368893231589: # Return MAX_EXP when we are in overflow mode return MAX_EXP x: int256 = unsafe_div(unsafe_mul(power, 2**96), 10**18) k: int256 = unsafe_div( unsafe_add( unsafe_div(unsafe_mul(x, 2**96), 54916777467707473351141471128), 2**95), 2**96) x = unsafe_sub(x, unsafe_mul(k, 54916777467707473351141471128)) y: int256 = unsafe_add(x, 1346386616545796478920950773328) y = unsafe_add(unsafe_div(unsafe_mul(y, x), 2**96), 57155421227552351082224309758442) p: int256 = unsafe_sub(unsafe_add(y, x), 94201549194550492254356042504812) p = unsafe_add(unsafe_div(unsafe_mul(p, y), 2**96), 28719021644029726153956944680412240) p = unsafe_add(unsafe_mul(p, x), (4385272521454847904659076985693276 * 2**96)) q: int256 = x - 2855989394907223263936484059900 q = unsafe_add(unsafe_div(unsafe_mul(q, x), 2**96), 50020603652535783019961831881945) q = unsafe_sub(unsafe_div(unsafe_mul(q, x), 2**96), 533845033583426703283633433725380) q = unsafe_add(unsafe_div(unsafe_mul(q, x), 2**96), 3604857256930695427073651918091429) q = unsafe_sub(unsafe_div(unsafe_mul(q, x), 2**96), 14423608567350463180887372962807573) q = unsafe_add(unsafe_div(unsafe_mul(q, x), 2**96), 26449188498355588339934803723976023) return shift( unsafe_mul(convert(unsafe_div(p, q), uint256), 3822833074963236453042738258902158003155416615667), unsafe_sub(k, 195)) @internal @pure def ln_int(_x: uint256) -> int256: """ @notice Logarithm ln() function based on log2. Not very gas-efficient but brief """ # adapted from: https://medium.com/coinmonks/9aef8515136e # and vyper log implementation # This can be much more optimal but that's not important here x: uint256 = _x if _x < 10**18: x = 10**36 / _x res: uint256 = 0 for i in range(8): t: uint256 = 2**(7 - i) p: uint256 = 2**t if x >= p * 10**18: x /= p res += t * 10**18 d: uint256 = 10**18 for i in range(59): # 18 decimals: math.log2(10**18) == 59.7 if (x >= 2 * 10**18): res += d x /= 2 x = x * x / 10**18 d /= 2 # Now res = log2(x) # ln(x) = log2(x) / log2(e) result: int256 = convert(res * 10**18 / 1442695040888963328, int256) if _x >= 10**18: return result else: return -result ### END MATH ### @internal @view def calculate_rate(_for: address, d_reserves: int256, d_debt: int256) -> uint256: total_debt: int256 = convert(Controller(_for).total_debt(), int256) total_reserves: int256 = convert(BORROWED_TOKEN.balanceOf(_for), int256) + total_debt + d_reserves total_debt += d_debt assert total_debt >= 0, "Negative debt" assert total_reserves >= total_debt, "Reserves too small" if total_debt == 0: return self.min_rate else: log_min_rate: int256 = self.log_min_rate log_max_rate: int256 = self.log_max_rate return self.exp(total_debt * (log_max_rate - log_min_rate) / total_reserves + log_min_rate) @view @external def rate(_for: address = msg.sender) -> uint256: return self.calculate_rate(_for, 0, 0) @external def rate_write(_for: address = msg.sender) -> uint256: return self.calculate_rate(_for, 0, 0) @external def set_rates(min_rate: uint256, max_rate: uint256): assert msg.sender == FACTORY.admin() assert max_rate >= min_rate assert min_rate >= MIN_RATE assert max_rate <= MAX_RATE if min_rate != self.min_rate: self.log_min_rate = self.ln_int(min_rate) if max_rate != self.max_rate: self.log_max_rate = self.ln_int(max_rate) self.min_rate = min_rate self.max_rate = max_rate log SetRates(min_rate, max_rate) @view @external def future_rate(_for: address, d_reserves: int256, d_debt: int256) -> uint256: return self.calculate_rate(_for, d_reserves, d_debt)