Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

scrvUSD oracle #1

Draft
wants to merge 2 commits into
base: master
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
81 changes: 81 additions & 0 deletions contracts/oracles/OptimismBlockHashOracle.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,81 @@
# pragma version 0.4.0
"""
@title Optimism Block Hash oracle
@notice A contract that saves L1 block hashes.
@license MIT
@author curve.fi
@custom:version 0.0.1
@custom:security [email protected]
"""

version: public(constant(String[8])) = "0.0.1"

interface IL1Block:
def number() -> uint64: view
def hash() -> bytes32: view


event CommitBlockHash:
committer: indexed(address)
number: indexed(uint256)
hash: bytes32

event ApplyBlockHash:
number: indexed(uint256)
hash: bytes32

L1_BLOCK: constant(IL1Block) = IL1Block(0x4200000000000000000000000000000000000015)

block_hash: public(HashMap[uint256, bytes32])
commitments: public(HashMap[address, HashMap[uint256, bytes32]])


@view
@external
def get_block_hash(_number: uint256) -> bytes32:
"""
@notice Query the block hash of a block.
@dev Reverts for block numbers which have yet to be set.
"""
block_hash: bytes32 = self.block_hash[_number]
assert block_hash != empty(bytes32)

return block_hash


@internal
def _update_block_hash() -> (uint256, bytes32):
number: uint256 = convert(staticcall L1_BLOCK.number(), uint256)
hash: bytes32 = staticcall L1_BLOCK.hash()
self.block_hash[number] = hash

return number, hash


@external
def commit() -> uint256:
"""
@notice Commit (and apply) a block hash.
@dev Same as `apply()` but saves committer
"""
number: uint256 = 0
hash: bytes32 = empty(bytes32)
number, hash = self._update_block_hash()

self.commitments[msg.sender][number] = hash
log CommitBlockHash(msg.sender, number, hash)
log ApplyBlockHash(number, hash)
return number


@external
def apply() -> uint256:
"""
@notice Apply a block hash.
"""
number: uint256 = 0
hash: bytes32 = empty(bytes32)
number, hash = self._update_block_hash()

log ApplyBlockHash(number, hash)
return number
219 changes: 219 additions & 0 deletions contracts/oracles/ScrvusdOracle.vy
Original file line number Diff line number Diff line change
@@ -0,0 +1,219 @@
# pragma version 0.4.0
"""
@title scrvUSD oracle
@notice Oracle of scrvUSD share price for StableSwap pool and other integrations.
Price updates are linearly smoothed with max acceleration to eliminate sharp changes.
@license Copyright (c) Curve.Fi, 2020-2024 - all rights reserved
@author curve.fi
@custom:version 0.0.1
@custom:security [email protected]
"""

version: public(constant(String[8])) = "0.0.1"

from snekmate.auth import ownable

initializes: ownable
exports: ownable.__interface__

event PriceUpdate:
new_price: uint256 # price to achieve
at: uint256 # timestamp at which price will be achieved

event SetProver:
prover: address

struct Interval:
previous: uint256
future: uint256


# scrvUSD Vault rate replication
# 0 total_debt
# 1 total_idle
ASSETS_PARAM_CNT: constant(uint256) = 2
# 0 totalSupply
# 1 full_profit_unlock_date
# 2 profit_unlocking_rate
# 3 last_profit_update
# 4 balance_of_self
# 5 block.timestamp
SUPPLY_PARAM_CNT: constant(uint256) = 6
MAX_BPS_EXTENDED: constant(uint256) = 1_000_000_000_000

prover: public(address)

price: public(Interval) # price of asset per share
time: public(Interval)

max_acceleration: public(uint256) # precision 10**18


@deploy
def __init__(_initial_price: uint256, _max_acceleration: uint256):
"""
@param _initial_price Initial price of asset per share (10**18)
@param _max_acceleration Maximum acceleration (10**12)
"""
self.price = Interval(previous=_initial_price, future=_initial_price)
self.time = Interval(previous=block.timestamp, future=block.timestamp)

self.max_acceleration = _max_acceleration

ownable.__init__()


@view
@internal
def _price_per_share(ts: uint256) -> uint256:
"""
@notice Using linear interpolation assuming updates are often enough
for absolute difference \approx relative difference
"""
price: Interval = self.price
time: Interval = self.time
if ts >= time.future:
return price.future
if ts <= time.previous:
return price.previous
return (price.previous * (time.future - ts) + price.future * (ts - time.previous)) // (time.future - time.previous)


@view
@external
def pricePerShare(ts: uint256=block.timestamp) -> uint256:
"""
@notice Get the price per share (pps) of the vault.
@dev NOT precise. Price is smoothed over time to eliminate sharp changes.
@param ts Timestamp to look price at. Only near future is supported.
@return The price per share.
"""
return self._price_per_share(ts)


@view
@external
def pricePerAsset(ts: uint256=block.timestamp) -> uint256:
"""
@notice Get the price per asset of the vault.
@dev NOT precise. Price is smoothed over time to eliminate sharp changes.
@param ts Timestamp to look price at. Only near future is supported.
@return The price per share.
"""
return 10 ** 36 // self._price_per_share(ts)


@view
@external
def price_oracle(i: uint256=0) -> uint256:
"""
@notice Alias of `pricePerShare` and `pricePerAsset` made for compatability
@param i 0 for scrvusd per crvusd, 1 for crvusd per scrvusd
@return Price with 10^18 precision
"""
return self._price_per_share(block.timestamp) if i == 0 else 10 ** 36 // self._price_per_share(block.timestamp)


@view
@internal
def _unlocked_shares(
full_profit_unlock_date: uint256,
profit_unlocking_rate: uint256,
last_profit_update: uint256,
balance_of_self: uint256,
ts: uint256,
) -> uint256:
"""
Returns the amount of shares that have been unlocked.
To avoid sudden price_per_share spikes, profits can be processed
through an unlocking period. The mechanism involves shares to be
minted to the vault which are unlocked gradually over time. Shares
that have been locked are gradually unlocked over profit_max_unlock_time.
"""
unlocked_shares: uint256 = 0
if full_profit_unlock_date > ts:
# If we have not fully unlocked, we need to calculate how much has been.
unlocked_shares = profit_unlocking_rate * (ts - last_profit_update) // MAX_BPS_EXTENDED

elif full_profit_unlock_date != 0:
# All shares have been unlocked
unlocked_shares = balance_of_self

return unlocked_shares


@view
@internal
def _total_supply(parameters: uint256[ASSETS_PARAM_CNT + SUPPLY_PARAM_CNT]) -> uint256:
# Need to account for the shares issued to the vault that have unlocked.
return parameters[ASSETS_PARAM_CNT + 0] -\
self._unlocked_shares(
parameters[ASSETS_PARAM_CNT + 1], # full_profit_unlock_date
parameters[ASSETS_PARAM_CNT + 2], # profit_unlocking_rate
parameters[ASSETS_PARAM_CNT + 3], # last_profit_update
parameters[ASSETS_PARAM_CNT + 4], # balance_of_self
parameters[ASSETS_PARAM_CNT + 5], # block.timestamp
)

@view
@internal
def _total_assets(parameters: uint256[ASSETS_PARAM_CNT + SUPPLY_PARAM_CNT]) -> uint256:
"""
@notice Total amount of assets that are in the vault and in the strategies.
"""
return parameters[0] + parameters[1]


@external
def update_price(
_parameters: uint256[ASSETS_PARAM_CNT + SUPPLY_PARAM_CNT],
) -> uint256:
"""
@notice Update price using `_parameters`
@param _parameters Parameters of
@return Relative price change of final price with 10^18 precision
"""
assert msg.sender == self.prover

current_price: uint256 = self._price_per_share(block.timestamp)
new_price: uint256 = self._total_assets(_parameters) * 10 ** 18 //\
self._total_supply(_parameters)

# Price is always growing and updates are never from future,
# hence allow only increasing updates
future_price: uint256 = self.price.future
if new_price > future_price:
self.price = Interval(previous=current_price, future=new_price)

rel_price_change: uint256 = (new_price - current_price) * 10 ** 18 // current_price + 1 # 1 for rounding up
future_ts: uint256 = block.timestamp + rel_price_change // self.max_acceleration
self.time = Interval(previous=block.timestamp, future=future_ts)

log PriceUpdate(new_price, future_ts)
return new_price * 10 ** 18 // future_price
return 10 ** 18


@external
def set_max_acceleration(_max_acceleration: uint256):
"""
@notice Set maximum acceleration of scrvUSD.
Must be less than StableSwap's minimum fee.
fee / (2 * block_time) is considered to be safe.
@param _max_acceleration Maximum acceleration (per sec)
"""
ownable._check_owner()

assert 10 ** 8 <= _max_acceleration and _max_acceleration <= 10 ** 18
self.max_acceleration = _max_acceleration


@external
def set_prover(_prover: address):
"""
@notice Set the account with prover permissions.
"""
ownable._check_owner()

self.prover = _prover
log SetProver(_prover)
Loading