diff --git a/contracts/oracles/OptimismBlockHashOracle.vy b/contracts/oracles/OptimismBlockHashOracle.vy new file mode 100644 index 0000000..8726257 --- /dev/null +++ b/contracts/oracles/OptimismBlockHashOracle.vy @@ -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 security@curve.fi +""" + +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 diff --git a/contracts/oracles/ScrvusdOracle.vy b/contracts/oracles/ScrvusdOracle.vy new file mode 100644 index 0000000..3e4e07d --- /dev/null +++ b/contracts/oracles/ScrvusdOracle.vy @@ -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 MIT +@author curve.fi +@custom:version 0.0.1 +@custom:security security@curve.fi +""" + +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 +SUPPLY_PARAM_CNT: constant(uint256) = 5 +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 +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 +def _total_supply(parameters: uint256[ALL_PARAM_CNT], ts: uint256) -> uint256: + # Need to account for the shares issued to the vault that have unlocked. + # return self.total_supply - self._unlocked_shares() + 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 + ts, # block.timestamp + ) + +@view +def _total_assets(parameters: uint256[ALL_PARAM_CNT]) -> uint256: + """ + @notice Total amount of assets that are in the vault and in the strategies. + """ + # return self.total_idle + self.total_debt + return parameters[0] + parameters[1] + + +@external +def update_price( + _parameters: uint256[ASSETS_PARAM_CNT + SUPPLY_PARAM_CNT], + _ts: uint256, +) -> uint256: + """ + @notice Update price using `_parameters` + @param _parameters Parameters of Yearn Vault to calculate scrvUSD price + @param _ts Timestamp at which these parameters are true + @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, _ts) + + # 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) diff --git a/contracts/oracles/ScrvusdOracleV1.vy b/contracts/oracles/ScrvusdOracleV1.vy new file mode 100644 index 0000000..e5c3086 --- /dev/null +++ b/contracts/oracles/ScrvusdOracleV1.vy @@ -0,0 +1,226 @@ +# 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 MIT +@author curve.fi +@custom:version 0.1.0 +@custom:security security@curve.fi +""" + +version: public(constant(String[8])) = "0.1.0" + +from snekmate.auth import ownable + +initializes: ownable +exports: ownable.__interface__ + +event PriceUpdate: + new_price: uint256 # price to achieve + price_params_ts: uint256 # timestamp at which price is recorded + +event SetProver: + prover: address + + +# 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 +SUPPLY_PARAM_CNT: constant(uint256) = 5 +ALL_PARAM_CNT: constant(uint256) = ASSETS_PARAM_CNT + SUPPLY_PARAM_CNT +MAX_BPS_EXTENDED: constant(uint256) = 1_000_000_000_000 + +prover: public(address) + +# smoothening +last_prices: uint256[2] +last_update: uint256 +# scrvusd replication parameters +price_params: uint256[ALL_PARAM_CNT] +price_params_ts: uint256 + +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.last_prices = [_initial_price, _initial_price] + self.last_update = block.timestamp + + # initial raw_price is 1 + self.price_params[0] = 1 # totalAssets = 1 + self.price_params[2] = 1 # totalSupply = 1 + + self.max_acceleration = _max_acceleration + + ownable.__init__() + + +@view +@external +def price_v0(_i: uint256=0) -> uint256: + """ + @notice Get lower bound of `scrvUSD.pricePerShare()` + @dev Price is updated in steps, need to prove every % changed + @param _i 0 (default) for `pricePerShare()` and 1 for `pricePerAsset()` + """ + return self._price_v0() if _i == 0 else 10**36 // self._price_v0() + + +@view +@external +def price_v1(_i: uint256=0) -> uint256: + """ + @notice Get approximate `scrvUSD.pricePerShare()` + @dev Price is simulated as if noone interacted to change `scrvUSD.pricePerShare()`, + need to adjust rate when too off. + @param _i 0 (default) for `pricePerShare()` and 1 for `pricePerAsset()` + """ + return self._price_v1() if _i == 0 else 10**36 // self._price_v1() + + +@view +@external +def raw_price(_i: uint256=0, _ts: uint256=block.timestamp) -> uint256: + """ + @notice Get approximate `scrvUSD.pricePerShare()` without smoothening + @param _i 0 (default) for `pricePerShare()` and 1 for `pricePerAsset()` + @param _ts Timestamp at which to see price (only near period is supported) + """ + return self._raw_price(_ts) if _i == 0 else 10**36 // self._raw_price(_ts) + + +@view +def _smoothed_price(last_price: uint256, ts: uint256) -> uint256: + raw_price: uint256 = self._raw_price(ts) + max_change: uint256 = self.max_acceleration * (block.timestamp - self.last_update) + # -max_change <= (raw_price - last_price) <= max_change + if unsafe_sub(raw_price + max_change, last_price) > 2 * max_change: + return last_price + max_change if raw_price > last_price else last_price - max_change + return raw_price + + +@view +def _price_v0() -> uint256: + return self._smoothed_price(self.last_prices[0], self.price_params_ts) + + +@view +def _price_v1() -> uint256: + return self._smoothed_price(self.last_prices[1], block.timestamp) + + +@view +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 +def _total_supply(parameters: uint256[ALL_PARAM_CNT], ts: uint256) -> uint256: + # Need to account for the shares issued to the vault that have unlocked. + # return self.total_supply - self._unlocked_shares() + 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 + ts, # block.timestamp + ) + +@view +def _total_assets(parameters: uint256[ALL_PARAM_CNT]) -> uint256: + """ + @notice Total amount of assets that are in the vault and in the strategies. + """ + # return self.total_idle + self.total_debt + return parameters[0] + parameters[1] + + +@view +def _raw_price(ts: uint256) -> uint256: + """ + @notice Price replication from scrvUSD vault + """ + parameters: uint256[ALL_PARAM_CNT] = self.price_params + return self._total_assets(parameters) * 10 ** 18 // self._total_supply(parameters, ts) + + +@external +def update_price(_parameters: uint256[ALL_PARAM_CNT], ts: uint256) -> uint256: + """ + @notice Update price using `_parameters` + @param _parameters Parameters of Yearn Vault to calculate scrvUSD price + @param ts Timestamp at which these parameters are true + @return Relative price change of final price with 10^18 precision + """ + assert msg.sender == self.prover + + self.last_prices = [self._price_v0(), self._price_v1()] + current_price: uint256 = self._raw_price(self.price_params_ts) + self.price_params = _parameters + self.price_params_ts = ts + new_price: uint256 = self._raw_price(ts) + # price is non-decreasing + assert current_price <= new_price, "Outdated" + + log PriceUpdate(new_price, ts) + return new_price * 10 ** 18 // current_price + + +@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) diff --git a/contracts/provers/ScrvusdProver.sol b/contracts/provers/ScrvusdProver.sol new file mode 100644 index 0000000..962c984 --- /dev/null +++ b/contracts/provers/ScrvusdProver.sol @@ -0,0 +1,100 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import {RLPReader} from "hamdiallam/Solidity-RLP@2.0.7/contracts/RLPReader.sol"; +import {StateProofVerifier as Verifier} from "../libs/StateProofVerifier.sol"; + +interface IBlockHashOracle { + function get_block_hash(uint256 _number) external view returns (bytes32); +} + +interface IScrvusdOracle { + function update_price( + uint256[2 + 5] memory _parameters, + uint256 ts + ) external returns (uint256); +} + +/// @title Scrvusd Prover +/// @author Curve Finance +contract ScrvusdProver { + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + + address constant SCRVUSD = + 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367; + bytes32 constant SCRVUSD_HASH = + keccak256(abi.encodePacked(SCRVUSD)); + + address public immutable BLOCK_HASH_ORACLE; + address public immutable SCRVUSD_ORACLE; + + uint256 constant PARAM_CNT = 2 + 5; + uint256 constant PROOF_CNT = 1 + PARAM_CNT; // account proof first + + constructor(address _block_hash_oracle, address _scrvusd_oracle) { + BLOCK_HASH_ORACLE = _block_hash_oracle; + SCRVUSD_ORACLE = _scrvusd_oracle; + } + + /// Prove parameters of scrvUSD rate. + /// @param _block_header_rlp The block header of any block. + /// @param _proof_rlp The state proof of the parameters. + function prove( + bytes memory _block_header_rlp, + bytes memory _proof_rlp + ) external returns (uint256) { + Verifier.BlockHeader memory block_header = Verifier.parseBlockHeader( + _block_header_rlp + ); + require(block_header.hash != bytes32(0)); // dev: invalid blockhash + require( + block_header.hash == + IBlockHashOracle(BLOCK_HASH_ORACLE).get_block_hash( + block_header.number + ) + ); // dev: blockhash mismatch + + // convert _proof_rlp into a list of `RLPItem`s + RLPReader.RLPItem[] memory proofs = _proof_rlp.toRlpItem().toList(); + require(proofs.length == PROOF_CNT); // dev: invalid number of proofs + + // 0th proof is the account proof for the scrvUSD contract + Verifier.Account memory account = Verifier.extractAccountFromProof( + SCRVUSD_HASH, // position of the account is the hash of its address + block_header.stateRootHash, + proofs[0].toList() + ); + require(account.exists); // dev: scrvUSD account does not exist + + // iterate over proofs + uint256[PROOF_CNT] memory PARAM_SLOTS = [ + uint256(0), // filler, account proof, no slot + + // Assets parameters + 21, // total_debt + 22, // total_idle + + // Supply parameters + 20, // totalSupply + 38, // full_profit_unlock_date + 39, // profit_unlocking_rate + 40, // last_profit_update + uint256(keccak256(abi.encode(18, SCRVUSD))) // balance_of_self + ]; + uint256[PARAM_CNT] memory params; + Verifier.SlotValue memory slot; + for (uint256 idx = 1; idx < PROOF_CNT; idx++) { + slot = Verifier.extractSlotValueFromProof( + keccak256(abi.encode(PARAM_SLOTS[idx])), + account.storageRoot, + proofs[idx].toList() + ); + // Some slots may not be used => not exist, e.g. total_debt + // require(slot.exists); + + params[idx - 1] = slot.value; + } + return IScrvusdOracle(SCRVUSD_ORACLE).update_price(params, block_header.timestamp); + } +} diff --git a/contracts/provers/ScrvusdProverTaiko.sol b/contracts/provers/ScrvusdProverTaiko.sol new file mode 100644 index 0000000..498af0b --- /dev/null +++ b/contracts/provers/ScrvusdProverTaiko.sol @@ -0,0 +1,97 @@ +// SPDX-License-Identifier: MIT +pragma solidity 0.8.18; + +import {RLPReader} from "hamdiallam/Solidity-RLP@2.0.7/contracts/RLPReader.sol"; +import {StateProofVerifier as Verifier} from "../libs/StateProofVerifier.sol"; + +interface ISignalService { + function getSyncedChainData(uint64 _chainId, bytes32 _kind, uint64 _blockId) + external view returns (uint64 blockId_, bytes32 chainData_); +} + +interface IScrvusdOracle { + function update_price( + uint256[2 + 5] memory _parameters, + uint256 ts + ) external returns (uint256); +} + +/// @title Scrvusd Prover +/// @author Curve Finance +contract ScrvusdProverTaiko { + using RLPReader for bytes; + using RLPReader for RLPReader.RLPItem; + + address constant SCRVUSD = + 0x0655977FEb2f289A4aB78af67BAB0d17aAb84367; + bytes32 constant SCRVUSD_HASH = + keccak256(abi.encodePacked(SCRVUSD)); + + address public constant SIGNAL_SERVICE = 0x1670000000000000000000000000000000000005; + address public immutable SCRVUSD_ORACLE; + + bytes32 internal constant H_STATE_ROOT = keccak256("STATE_ROOT"); + + uint256 constant PARAM_CNT = 2 + 5; + uint256 constant PROOF_CNT = 1 + PARAM_CNT; // account proof first + + constructor(address _scrvusd_oracle) { + SCRVUSD_ORACLE = _scrvusd_oracle; + } + + /// Prove parameters of scrvUSD rate. + /// @param _block_number The block number of known block + /// @param _proof_rlp The state proof of the parameters. + function prove( + uint64 _block_number, + bytes memory _proof_rlp + ) external returns (uint256) { + // convert _proof_rlp into a list of `RLPItem`s + RLPReader.RLPItem[] memory proofs = _proof_rlp.toRlpItem().toList(); + require(proofs.length == PROOF_CNT); // dev: invalid number of proofs + + // get state root hash + uint64 blockId = 0; + bytes32 stateRoot = 0; + (blockId, stateRoot) = ISignalService(SIGNAL_SERVICE).getSyncedChainData(1, H_STATE_ROOT, _block_number); + + // 0th proof is the account proof for the scrvUSD contract + Verifier.Account memory account = Verifier.extractAccountFromProof( + SCRVUSD_HASH, // position of the account is the hash of its address + stateRoot, // State root hash + proofs[0].toList() + ); + require(account.exists); // dev: scrvUSD account does not exist + + // iterate over proofs + uint256[PROOF_CNT] memory PARAM_SLOTS = [ + 0, // filler (account proof) + + // Assets parameters + uint256(21), // total_debt + 22, // total_idle + + // Supply parameters + 20, // totalSupply + 38, // full_profit_unlock_date + 39, // profit_unlocking_rate + 40, // last_profit_update + uint256(keccak256(abi.encode(18, SCRVUSD))) // balance_of_self + ]; + uint256[PARAM_CNT] memory params; + Verifier.SlotValue memory slot; + for (uint256 idx = 1; idx < PROOF_CNT; idx++) { + slot = Verifier.extractSlotValueFromProof( + keccak256(abi.encode(PARAM_SLOTS[idx])), + account.storageRoot, + proofs[idx].toList() + ); + // Some slots may not be used => not exist, e.g. total_idle + // require(slot.exists); + + params[idx - 1] = slot.value; + } + // block.timestamp not available, using `last_profit_update` + return IScrvusdOracle(SCRVUSD_ORACLE).update_price(params, params[5]); + } +} diff --git a/scripts/scrvusd_keeper.py b/scripts/scrvusd_keeper.py new file mode 100644 index 0000000..b5afa6a --- /dev/null +++ b/scripts/scrvusd_keeper.py @@ -0,0 +1,196 @@ +import time +from time import sleep + +from web3 import Web3 +from web3.contract import Contract +import json +import os + +from getpass import getpass +from eth_account import account, Account + +from submit_scrvusd_price import generate_proof + + +CHAIN = "optimism" # ALTER +BLOCK_NUMBER = None # ALTER, None will take latest +ETH_NETWORK = f"https://eth-mainnet.alchemyapi.io/v2/{os.environ['WEB3_ETHEREUM_MAINNET_ALCHEMY_API_KEY']}" # ALTER +L2_NETWORK = { # ALTER + "optimism": f"https://opt-mainnet.g.alchemy.com/v2/{os.environ['WEB3_OPTIMISM_MAINNET_ALCHEMY_API_KEY']}", + "base": f"https://mainnet.base.org", + "fraxtal": f"https://rpc.frax.com", + "mantle": f"https://rpc.mantle.xyz/", + "arbitrum": f"https://arb-mainnet.g.alchemy.com/v2/{os.environ['WEB3_ARBITRUM_MAINNET_ALCHEMY_API_KEY']}", + "taiko": f"https://rpc.taiko.xyz" +}[CHAIN] + +SCRVUSD = "0x0655977FEb2f289A4aB78af67BAB0d17aAb84367" + +B_ORACLE, S_ORACLE, PROVER = { + "optimism": ("0x988d1037e9608B21050A8EFba0c6C45e01A3Bce7", "0xC772063cE3e622B458B706Dd2e36309418A1aE42", "0x47ca04Ee05f167583122833abfb0f14aC5677Ee4"), + "base": ("0x3c0a405E914337139992625D5100Ea141a9C4d11", "0x3d8EADb739D1Ef95dd53D718e4810721837c69c1", "0x6a2691068C7CbdA03292Ba0f9c77A25F658bAeF5"), + "fraxtal": ("0xbD2775B8eADaE81501898eB208715f0040E51882", "0x09F8D940EAD55853c51045bcbfE67341B686C071", "0x0094Ad026643994c8fB2136ec912D508B15fe0E5"), + "mantle": ("0x004A476B5B76738E34c86C7144554B9d34402F13", "0xbD2775B8eADaE81501898eB208715f0040E51882", "0x09F8D940EAD55853c51045bcbfE67341B686C071"), + # "arbitrum": ("0x47ca04Ee05f167583122833abfb0f14aC5677Ee4", "0x3195A313F409714e1f173ca095Dba7BfBb5767F7", "0x8Fb3Ec8f2d1Dc089E70CD61f1E49496d443B2124"), redeploy? + "taiko": {"0x1670000000000000000000000000000000000005", "0x070A5C8a99002F50C18B52B90e938BC477611b16", "0x004A476B5B76738E34c86C7144554B9d34402F13"}, +}[CHAIN] +VERSION = { + "optimism": "ScrvusdOracle", + "base": "ScrvusdOracle", + "fraxtal": "ScrvusdOracle", + "mantle": "ScrvusdOracle", + # "arbitrum": "ScrvusdOracle", + "taiko": "ScrvusdOracleV1", +}[CHAIN] + +last_update = 0 # time.time() +REL_CHANGE_THRESHOLD = 1.00005 # 0.5 bps, should be >1 + +APPLY_BLOCK_HASH = Web3.keccak(text="ApplyBlockHash(uint256,bytes32)").hex() +COMMIT_BLOCK_HASH = Web3.keccak(text="CommitBlockHash(address,uint256,bytes32)").hex() + + +eth_web3 = Web3( + provider=Web3.HTTPProvider( + ETH_NETWORK, + # {"verify_ssl": False}, + ), + # modules={"eth": (AsyncEth,)}, +) + +l2_web3 = Web3( + provider=Web3.HTTPProvider( + L2_NETWORK, + # {"verify_ssl": False}, + ), + # modules={"eth": (AsyncEth,)}, +) + + +def account_load_pkey(fname): + path = os.path.expanduser(os.path.join('~', '.brownie', 'accounts', fname + '.json')) + with open(path, 'r') as f: + pkey = account.decode_keyfile_json(json.load(f), getpass()) + return pkey +wallet = Account.from_key(account_load_pkey("curve")) # ALTER + + +def _retrieve_last_applied_block(baddr, logs) -> (int, int): + block_number = -1 + apply_block_number = -1 + for log in logs: + if log["address"] != baddr: + continue + if log.get("topics") and log["topics"][0].hex() == APPLY_BLOCK_HASH: + block_number = max(block_number, int(log["topics"][1].hex(), 16)) + apply_block_number = max(apply_block_number, int(log["blockNumber"])) + if log.get("event") and log["event"] == "ApplyBlockHash": + block_number = max(block_number, log["args"]["number"]) + apply_block_number = max(apply_block_number, int(log["blockNumber"])) + return block_number, apply_block_number + + +def fetch_block_number(boracle): + if CHAIN in ["taiko"]: + # Fetch last available + to = l2_web3.eth.block_number + logs = boracle.events.ChainDataSynced().get_logs( + from_block=to - 100, + to_block=to, + argument_filters={ + "chainId": 1, + "kind": "0x73e6d340850343cc6f001515dc593377337c95a6ffe034fe1e844d4dab5da169", + } + ) + block_number = max(map(lambda log: log["args"]["blockId"], logs)) + time.sleep(1) + print(f"Fetched block: {block_number}") + else: + # Apply latest available blockhash + tx = boracle.functions.apply().build_transaction( + { + "from": wallet.address, + "nonce": l2_web3.eth.get_transaction_count(wallet.address), + } + ) + signed_tx = l2_web3.eth.account.sign_transaction(tx, private_key=wallet.key) + tx_hash = l2_web3.eth.send_raw_transaction(signed_tx.raw_transaction) + l2_web3.eth.wait_for_transaction_receipt(tx_hash) + tx_receipt = l2_web3.eth.get_transaction_receipt(tx_hash) + block_number, _ = _retrieve_last_applied_block(boracle.address, tx_receipt["logs"]) + assert block_number > 0, "Applied block number not retrieved" + print(f"Applied block: {block_number}") + time.sleep(1) + return block_number + + +def prove(boracle, prover, block_number=None): + if not block_number: + block_number = fetch_block_number(boracle) + + proofs = generate_proof(eth_web3, block_number) + + if CHAIN in ["taiko"]: + if isinstance(prover, Contract): + tx = prover.functions.prove(block_number, bytes.fromhex(proofs[1])).build_transaction( + { + "from": wallet.address, + "nonce": l2_web3.eth.get_transaction_count(wallet.address), + } + ) + signed_tx = l2_web3.eth.account.sign_transaction(tx, private_key=wallet.key) + l2_web3.eth.send_raw_transaction(signed_tx.raw_transaction) + l2_web3.eth.wait_for_transaction_receipt(signed_tx) + else: + prover.prove(block_number, bytes.fromhex(proofs[1])) + else: + tx = prover.functions.prove(bytes.fromhex(proofs[0]), bytes.fromhex(proofs[1])).build_transaction( + { + "from": wallet.address, + "nonce": l2_web3.eth.get_transaction_count(wallet.address), + } + ) + signed_tx = l2_web3.eth.account.sign_transaction(tx, private_key=wallet.key) + l2_web3.eth.send_raw_transaction(signed_tx.raw_transaction) + l2_web3.eth.wait_for_transaction_receipt(signed_tx) + print(f"Submitted proof") + + +def time_to_update(scrvusd, soracle): + # can be any relative change or time + if time.time() - last_update >= 4 * 3600: # Every 4 hours + return True + price = scrvusd.functions.pricePerShare().call() + if VERSION == "ScrvusdOracle": + oracle_price = soracle.functions.price().call()[1] # take price.future = latest set + elif VERSION == "ScrvusdOracleV1": + oracle_price = soracle.functions.raw_price().call() + return price / oracle_price > REL_CHANGE_THRESHOLD + + +def loop(): + scrvusd = eth_web3.eth.contract(SCRVUSD, abi=[{'name': 'Deposit', 'inputs': [{'name': 'sender', 'type': 'address', 'indexed': True}, {'name': 'owner', 'type': 'address', 'indexed': True}, {'name': 'assets', 'type': 'uint256', 'indexed': False}, {'name': 'shares', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'Withdraw', 'inputs': [{'name': 'sender', 'type': 'address', 'indexed': True}, {'name': 'receiver', 'type': 'address', 'indexed': True}, {'name': 'owner', 'type': 'address', 'indexed': True}, {'name': 'assets', 'type': 'uint256', 'indexed': False}, {'name': 'shares', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'Transfer', 'inputs': [{'name': 'sender', 'type': 'address', 'indexed': True}, {'name': 'receiver', 'type': 'address', 'indexed': True}, {'name': 'value', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'Approval', 'inputs': [{'name': 'owner', 'type': 'address', 'indexed': True}, {'name': 'spender', 'type': 'address', 'indexed': True}, {'name': 'value', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'StrategyChanged', 'inputs': [{'name': 'strategy', 'type': 'address', 'indexed': True}, {'name': 'change_type', 'type': 'uint256', 'indexed': True}], 'anonymous': False, 'type': 'event'}, {'name': 'StrategyReported', 'inputs': [{'name': 'strategy', 'type': 'address', 'indexed': True}, {'name': 'gain', 'type': 'uint256', 'indexed': False}, {'name': 'loss', 'type': 'uint256', 'indexed': False}, {'name': 'current_debt', 'type': 'uint256', 'indexed': False}, {'name': 'protocol_fees', 'type': 'uint256', 'indexed': False}, {'name': 'total_fees', 'type': 'uint256', 'indexed': False}, {'name': 'total_refunds', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'DebtUpdated', 'inputs': [{'name': 'strategy', 'type': 'address', 'indexed': True}, {'name': 'current_debt', 'type': 'uint256', 'indexed': False}, {'name': 'new_debt', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'RoleSet', 'inputs': [{'name': 'account', 'type': 'address', 'indexed': True}, {'name': 'role', 'type': 'uint256', 'indexed': True}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateFutureRoleManager', 'inputs': [{'name': 'future_role_manager', 'type': 'address', 'indexed': True}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateRoleManager', 'inputs': [{'name': 'role_manager', 'type': 'address', 'indexed': True}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateAccountant', 'inputs': [{'name': 'accountant', 'type': 'address', 'indexed': True}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateDepositLimitModule', 'inputs': [{'name': 'deposit_limit_module', 'type': 'address', 'indexed': True}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateWithdrawLimitModule', 'inputs': [{'name': 'withdraw_limit_module', 'type': 'address', 'indexed': True}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateDefaultQueue', 'inputs': [{'name': 'new_default_queue', 'type': 'address[]', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateUseDefaultQueue', 'inputs': [{'name': 'use_default_queue', 'type': 'bool', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateAutoAllocate', 'inputs': [{'name': 'auto_allocate', 'type': 'bool', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdatedMaxDebtForStrategy', 'inputs': [{'name': 'sender', 'type': 'address', 'indexed': True}, {'name': 'strategy', 'type': 'address', 'indexed': True}, {'name': 'new_debt', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateDepositLimit', 'inputs': [{'name': 'deposit_limit', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateMinimumTotalIdle', 'inputs': [{'name': 'minimum_total_idle', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'UpdateProfitMaxUnlockTime', 'inputs': [{'name': 'profit_max_unlock_time', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'DebtPurchased', 'inputs': [{'name': 'strategy', 'type': 'address', 'indexed': True}, {'name': 'amount', 'type': 'uint256', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'Shutdown', 'inputs': [], 'anonymous': False, 'type': 'event'}, {'stateMutability': 'nonpayable', 'type': 'constructor', 'inputs': [], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'initialize', 'inputs': [{'name': 'asset', 'type': 'address'}, {'name': 'name', 'type': 'string'}, {'name': 'symbol', 'type': 'string'}, {'name': 'role_manager', 'type': 'address'}, {'name': 'profit_max_unlock_time', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'setName', 'inputs': [{'name': 'name', 'type': 'string'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'setSymbol', 'inputs': [{'name': 'symbol', 'type': 'string'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_accountant', 'inputs': [{'name': 'new_accountant', 'type': 'address'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_default_queue', 'inputs': [{'name': 'new_default_queue', 'type': 'address[]'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_use_default_queue', 'inputs': [{'name': 'use_default_queue', 'type': 'bool'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_auto_allocate', 'inputs': [{'name': 'auto_allocate', 'type': 'bool'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_deposit_limit', 'inputs': [{'name': 'deposit_limit', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_deposit_limit', 'inputs': [{'name': 'deposit_limit', 'type': 'uint256'}, {'name': 'override', 'type': 'bool'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_deposit_limit_module', 'inputs': [{'name': 'deposit_limit_module', 'type': 'address'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_deposit_limit_module', 'inputs': [{'name': 'deposit_limit_module', 'type': 'address'}, {'name': 'override', 'type': 'bool'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_withdraw_limit_module', 'inputs': [{'name': 'withdraw_limit_module', 'type': 'address'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_minimum_total_idle', 'inputs': [{'name': 'minimum_total_idle', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'setProfitMaxUnlockTime', 'inputs': [{'name': 'new_profit_max_unlock_time', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'set_role', 'inputs': [{'name': 'account', 'type': 'address'}, {'name': 'role', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'add_role', 'inputs': [{'name': 'account', 'type': 'address'}, {'name': 'role', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'remove_role', 'inputs': [{'name': 'account', 'type': 'address'}, {'name': 'role', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'transfer_role_manager', 'inputs': [{'name': 'role_manager', 'type': 'address'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'accept_role_manager', 'inputs': [], 'outputs': []}, {'stateMutability': 'view', 'type': 'function', 'name': 'isShutdown', 'inputs': [], 'outputs': [{'name': '', 'type': 'bool'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'unlockedShares', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'pricePerShare', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'get_default_queue', 'inputs': [], 'outputs': [{'name': '', 'type': 'address[]'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'process_report', 'inputs': [{'name': 'strategy', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}, {'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'buy_debt', 'inputs': [{'name': 'strategy', 'type': 'address'}, {'name': 'amount', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'add_strategy', 'inputs': [{'name': 'new_strategy', 'type': 'address'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'add_strategy', 'inputs': [{'name': 'new_strategy', 'type': 'address'}, {'name': 'add_to_queue', 'type': 'bool'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'revoke_strategy', 'inputs': [{'name': 'strategy', 'type': 'address'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'force_revoke_strategy', 'inputs': [{'name': 'strategy', 'type': 'address'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'update_max_debt_for_strategy', 'inputs': [{'name': 'strategy', 'type': 'address'}, {'name': 'new_max_debt', 'type': 'uint256'}], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'update_debt', 'inputs': [{'name': 'strategy', 'type': 'address'}, {'name': 'target_debt', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'update_debt', 'inputs': [{'name': 'strategy', 'type': 'address'}, {'name': 'target_debt', 'type': 'uint256'}, {'name': 'max_loss', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'shutdown_vault', 'inputs': [], 'outputs': []}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'deposit', 'inputs': [{'name': 'assets', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'mint', 'inputs': [{'name': 'shares', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'withdraw', 'inputs': [{'name': 'assets', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}, {'name': 'owner', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'withdraw', 'inputs': [{'name': 'assets', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}, {'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'withdraw', 'inputs': [{'name': 'assets', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}, {'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}, {'name': 'strategies', 'type': 'address[]'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'redeem', 'inputs': [{'name': 'shares', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}, {'name': 'owner', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'redeem', 'inputs': [{'name': 'shares', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}, {'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'redeem', 'inputs': [{'name': 'shares', 'type': 'uint256'}, {'name': 'receiver', 'type': 'address'}, {'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}, {'name': 'strategies', 'type': 'address[]'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'approve', 'inputs': [{'name': 'spender', 'type': 'address'}, {'name': 'amount', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'bool'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'transfer', 'inputs': [{'name': 'receiver', 'type': 'address'}, {'name': 'amount', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'bool'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'transferFrom', 'inputs': [{'name': 'sender', 'type': 'address'}, {'name': 'receiver', 'type': 'address'}, {'name': 'amount', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'bool'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'permit', 'inputs': [{'name': 'owner', 'type': 'address'}, {'name': 'spender', 'type': 'address'}, {'name': 'amount', 'type': 'uint256'}, {'name': 'deadline', 'type': 'uint256'}, {'name': 'v', 'type': 'uint8'}, {'name': 'r', 'type': 'bytes32'}, {'name': 's', 'type': 'bytes32'}], 'outputs': [{'name': '', 'type': 'bool'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'balanceOf', 'inputs': [{'name': 'addr', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'totalSupply', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'totalAssets', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'totalIdle', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'totalDebt', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'convertToShares', 'inputs': [{'name': 'assets', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'previewDeposit', 'inputs': [{'name': 'assets', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'previewMint', 'inputs': [{'name': 'shares', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'convertToAssets', 'inputs': [{'name': 'shares', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxDeposit', 'inputs': [{'name': 'receiver', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxMint', 'inputs': [{'name': 'receiver', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxWithdraw', 'inputs': [{'name': 'owner', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxWithdraw', 'inputs': [{'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxWithdraw', 'inputs': [{'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}, {'name': 'strategies', 'type': 'address[]'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxRedeem', 'inputs': [{'name': 'owner', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxRedeem', 'inputs': [{'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'maxRedeem', 'inputs': [{'name': 'owner', 'type': 'address'}, {'name': 'max_loss', 'type': 'uint256'}, {'name': 'strategies', 'type': 'address[]'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'previewWithdraw', 'inputs': [{'name': 'assets', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'previewRedeem', 'inputs': [{'name': 'shares', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'FACTORY', 'inputs': [], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'pure', 'type': 'function', 'name': 'apiVersion', 'inputs': [], 'outputs': [{'name': '', 'type': 'string'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'assess_share_of_unrealised_losses', 'inputs': [{'name': 'strategy', 'type': 'address'}, {'name': 'assets_needed', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'profitMaxUnlockTime', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'fullProfitUnlockDate', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'profitUnlockingRate', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'lastProfitUpdate', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'DOMAIN_SEPARATOR', 'inputs': [], 'outputs': [{'name': '', 'type': 'bytes32'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'asset', 'inputs': [], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'decimals', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint8'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'strategies', 'inputs': [{'name': 'arg0', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'tuple', 'components': [{'name': 'activation', 'type': 'uint256'}, {'name': 'last_report', 'type': 'uint256'}, {'name': 'current_debt', 'type': 'uint256'}, {'name': 'max_debt', 'type': 'uint256'}]}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'default_queue', 'inputs': [{'name': 'arg0', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'use_default_queue', 'inputs': [], 'outputs': [{'name': '', 'type': 'bool'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'auto_allocate', 'inputs': [], 'outputs': [{'name': '', 'type': 'bool'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'allowance', 'inputs': [{'name': 'arg0', 'type': 'address'}, {'name': 'arg1', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'minimum_total_idle', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'deposit_limit', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'accountant', 'inputs': [], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'deposit_limit_module', 'inputs': [], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'withdraw_limit_module', 'inputs': [], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'roles', 'inputs': [{'name': 'arg0', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'role_manager', 'inputs': [], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'future_role_manager', 'inputs': [], 'outputs': [{'name': '', 'type': 'address'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'name', 'inputs': [], 'outputs': [{'name': '', 'type': 'string'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'symbol', 'inputs': [], 'outputs': [{'name': '', 'type': 'string'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'nonces', 'inputs': [{'name': 'arg0', 'type': 'address'}], 'outputs': [{'name': '', 'type': 'uint256'}]}]) + if CHAIN in ["taiko"]: + boracle = l2_web3.eth.contract(B_ORACLE, abi=[{'inputs': [], 'name': 'FUNC_NOT_IMPLEMENTED', 'type': 'error'}, {'inputs': [], 'name': 'INVALID_PAUSE_STATUS', 'type': 'error'}, {'inputs': [], 'name': 'LTP_INVALID_ACCOUNT_PROOF', 'type': 'error'}, {'inputs': [], 'name': 'LTP_INVALID_INCLUSION_PROOF', 'type': 'error'}, {'inputs': [], 'name': 'REENTRANT_CALL', 'type': 'error'}, {'inputs': [], 'name': 'RESOLVER_DENIED', 'type': 'error'}, {'inputs': [], 'name': 'RESOLVER_INVALID_MANAGER', 'type': 'error'}, {'inputs': [], 'name': 'RESOLVER_UNEXPECTED_CHAINID', 'type': 'error'}, {'inputs': [{'internalType': 'uint64', 'name': 'chainId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': 'name', 'type': 'bytes32'}], 'name': 'RESOLVER_ZERO_ADDR', 'type': 'error'}, {'inputs': [], 'name': 'SS_EMPTY_PROOF', 'type': 'error'}, {'inputs': [], 'name': 'SS_INVALID_HOPS_WITH_LOOP', 'type': 'error'}, {'inputs': [], 'name': 'SS_INVALID_LAST_HOP_CHAINID', 'type': 'error'}, {'inputs': [], 'name': 'SS_INVALID_MID_HOP_CHAINID', 'type': 'error'}, {'inputs': [], 'name': 'SS_INVALID_STATE', 'type': 'error'}, {'inputs': [], 'name': 'SS_SIGNAL_NOT_FOUND', 'type': 'error'}, {'inputs': [], 'name': 'SS_UNAUTHORIZED', 'type': 'error'}, {'inputs': [], 'name': 'ZERO_ADDRESS', 'type': 'error'}, {'inputs': [], 'name': 'ZERO_VALUE', 'type': 'error'}, {'anonymous': False, 'inputs': [{'indexed': False, 'internalType': 'address', 'name': 'previousAdmin', 'type': 'address'}, {'indexed': False, 'internalType': 'address', 'name': 'newAdmin', 'type': 'address'}], 'name': 'AdminChanged', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'addr', 'type': 'address'}, {'indexed': False, 'internalType': 'bool', 'name': 'authorized', 'type': 'bool'}], 'name': 'Authorized', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'beacon', 'type': 'address'}], 'name': 'BeaconUpgraded', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'uint64', 'name': 'chainId', 'type': 'uint64'}, {'indexed': True, 'internalType': 'uint64', 'name': 'blockId', 'type': 'uint64'}, {'indexed': True, 'internalType': 'bytes32', 'name': 'kind', 'type': 'bytes32'}, {'indexed': False, 'internalType': 'bytes32', 'name': 'data', 'type': 'bytes32'}, {'indexed': False, 'internalType': 'bytes32', 'name': 'signal', 'type': 'bytes32'}], 'name': 'ChainDataSynced', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': False, 'internalType': 'uint8', 'name': 'version', 'type': 'uint8'}], 'name': 'Initialized', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'previousOwner', 'type': 'address'}, {'indexed': True, 'internalType': 'address', 'name': 'newOwner', 'type': 'address'}], 'name': 'OwnershipTransferStarted', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'previousOwner', 'type': 'address'}, {'indexed': True, 'internalType': 'address', 'name': 'newOwner', 'type': 'address'}], 'name': 'OwnershipTransferred', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': False, 'internalType': 'address', 'name': 'account', 'type': 'address'}], 'name': 'Paused', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': False, 'internalType': 'address', 'name': 'app', 'type': 'address'}, {'indexed': False, 'internalType': 'bytes32', 'name': 'signal', 'type': 'bytes32'}, {'indexed': False, 'internalType': 'bytes32', 'name': 'slot', 'type': 'bytes32'}, {'indexed': False, 'internalType': 'bytes32', 'name': 'value', 'type': 'bytes32'}], 'name': 'SignalSent', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': False, 'internalType': 'address', 'name': 'account', 'type': 'address'}], 'name': 'Unpaused', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'internalType': 'address', 'name': 'implementation', 'type': 'address'}], 'name': 'Upgraded', 'type': 'event'}, {'inputs': [], 'name': 'acceptOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'addressManager', 'outputs': [{'internalType': 'address', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'address', 'name': '_addr', 'type': 'address'}, {'internalType': 'bool', 'name': '_authorize', 'type': 'bool'}], 'name': 'authorize', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'address', 'name': '_app', 'type': 'address'}, {'internalType': 'bytes32', 'name': '_signal', 'type': 'bytes32'}], 'name': 'getSignalSlot', 'outputs': [{'internalType': 'bytes32', 'name': '', 'type': 'bytes32'}], 'stateMutability': 'pure', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': '_kind', 'type': 'bytes32'}, {'internalType': 'uint64', 'name': '_blockId', 'type': 'uint64'}], 'name': 'getSyncedChainData', 'outputs': [{'internalType': 'uint64', 'name': 'blockId_', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': 'chainData_', 'type': 'bytes32'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'impl', 'outputs': [{'internalType': 'address', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'inNonReentrant', 'outputs': [{'internalType': 'bool', 'name': '', 'type': 'bool'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'address', 'name': '_owner', 'type': 'address'}, {'internalType': 'address', 'name': '_addressManager', 'type': 'address'}], 'name': 'init', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'internalType': 'address', 'name': 'addr', 'type': 'address'}], 'name': 'isAuthorized', 'outputs': [{'internalType': 'bool', 'name': 'authorized', 'type': 'bool'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': '_kind', 'type': 'bytes32'}, {'internalType': 'uint64', 'name': '_blockId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': '_chainData', 'type': 'bytes32'}], 'name': 'isChainDataSynced', 'outputs': [{'internalType': 'bool', 'name': '', 'type': 'bool'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'address', 'name': '_app', 'type': 'address'}, {'internalType': 'bytes32', 'name': '_signal', 'type': 'bytes32'}], 'name': 'isSignalSent', 'outputs': [{'internalType': 'bool', 'name': '', 'type': 'bool'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'lastUnpausedAt', 'outputs': [{'internalType': 'uint64', 'name': '', 'type': 'uint64'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'owner', 'outputs': [{'internalType': 'address', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'pause', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'paused', 'outputs': [{'internalType': 'bool', 'name': '', 'type': 'bool'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'pendingOwner', 'outputs': [{'internalType': 'address', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'address', 'name': '_app', 'type': 'address'}, {'internalType': 'bytes32', 'name': '_signal', 'type': 'bytes32'}, {'internalType': 'bytes', 'name': '_proof', 'type': 'bytes'}], 'name': 'proveSignalReceived', 'outputs': [{'internalType': 'uint256', 'name': 'numCacheOps_', 'type': 'uint256'}], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'proxiableUUID', 'outputs': [{'internalType': 'bytes32', 'name': '', 'type': 'bytes32'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'renounceOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': '_name', 'type': 'bytes32'}, {'internalType': 'bool', 'name': '_allowZeroAddress', 'type': 'bool'}], 'name': 'resolve', 'outputs': [{'internalType': 'addresspayable', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'bytes32', 'name': '_name', 'type': 'bytes32'}, {'internalType': 'bool', 'name': '_allowZeroAddress', 'type': 'bool'}], 'name': 'resolve', 'outputs': [{'internalType': 'addresspayable', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'bytes32', 'name': '_signal', 'type': 'bytes32'}], 'name': 'sendSignal', 'outputs': [{'internalType': 'bytes32', 'name': '', 'type': 'bytes32'}], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': '_kind', 'type': 'bytes32'}, {'internalType': 'uint64', 'name': '_blockId', 'type': 'uint64'}], 'name': 'signalForChainData', 'outputs': [{'internalType': 'bytes32', 'name': '', 'type': 'bytes32'}], 'stateMutability': 'pure', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': '_kind', 'type': 'bytes32'}, {'internalType': 'uint64', 'name': '_blockId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': '_chainData', 'type': 'bytes32'}], 'name': 'syncChainData', 'outputs': [{'internalType': 'bytes32', 'name': '', 'type': 'bytes32'}], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': 'chainId', 'type': 'uint64'}, {'internalType': 'bytes32', 'name': 'kind', 'type': 'bytes32'}], 'name': 'topBlockId', 'outputs': [{'internalType': 'uint64', 'name': 'blockId', 'type': 'uint64'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'address', 'name': 'newOwner', 'type': 'address'}], 'name': 'transferOwnership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'unpause', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'internalType': 'address', 'name': 'newImplementation', 'type': 'address'}], 'name': 'upgradeTo', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'internalType': 'address', 'name': 'newImplementation', 'type': 'address'}, {'internalType': 'bytes', 'name': 'data', 'type': 'bytes'}], 'name': 'upgradeToAndCall', 'outputs': [], 'stateMutability': 'payable', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_chainId', 'type': 'uint64'}, {'internalType': 'address', 'name': '_app', 'type': 'address'}, {'internalType': 'bytes32', 'name': '_signal', 'type': 'bytes32'}, {'internalType': 'bytes', 'name': '_proof', 'type': 'bytes'}], 'name': 'verifySignalReceived', 'outputs': [], 'stateMutability': 'view', 'type': 'function'}]) + prover = l2_web3.eth.contract(PROVER, abi=[{'inputs': [{'internalType': 'address', 'name': '_scrvusd_oracle', 'type': 'address'}], 'stateMutability': 'nonpayable', 'type': 'constructor'}, {'inputs': [], 'name': 'SCRVUSD_ORACLE', 'outputs': [{'internalType': 'address', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'SIGNAL_SERVICE', 'outputs': [{'internalType': 'address', 'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'internalType': 'uint64', 'name': '_block_number', 'type': 'uint64'}, {'internalType': 'bytes', 'name': '_proof_rlp', 'type': 'bytes'}], 'name': 'prove', 'outputs': [{'internalType': 'uint256', 'name': '', 'type': 'uint256'}], 'stateMutability': 'nonpayable', 'type': 'function'}]) + else: + boracle = l2_web3.eth.contract(B_ORACLE, abi=[{'name': 'CommitBlockHash', 'inputs': [{'name': 'committer', 'type': 'address', 'indexed': True}, {'name': 'number', 'type': 'uint256', 'indexed': True}, {'name': 'hash', 'type': 'bytes32', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'name': 'ApplyBlockHash', 'inputs': [{'name': 'number', 'type': 'uint256', 'indexed': True}, {'name': 'hash', 'type': 'bytes32', 'indexed': False}], 'anonymous': False, 'type': 'event'}, {'stateMutability': 'view', 'type': 'function', 'name': 'get_block_hash', 'inputs': [{'name': '_number', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'bytes32'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'commit', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'nonpayable', 'type': 'function', 'name': 'apply', 'inputs': [], 'outputs': [{'name': '', 'type': 'uint256'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'block_hash', 'inputs': [{'name': 'arg0', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'bytes32'}]}, {'stateMutability': 'view', 'type': 'function', 'name': 'commitments', 'inputs': [{'name': 'arg0', 'type': 'address'}, {'name': 'arg1', 'type': 'uint256'}], 'outputs': [{'name': '', 'type': 'bytes32'}]}]) + prover = l2_web3.eth.contract(PROVER, abi=[{"inputs": [{"internalType": "bytes", "name": "_block_header_rlp", "type": "bytes"}, {"internalType": "bytes", "name": "_proof_rlp", "type": "bytes"}], "name": "prove", "outputs": [{"internalType": "uint256", "name": "", "type": "uint256"}], "stateMutability": "nonpayable", "type": "function"}]) + if VERSION == "ScrvusdOracle": + soracle = l2_web3.eth.contract(S_ORACLE, abi=[{'anonymous': False, 'inputs': [{'indexed': False, 'name': 'new_price', 'type': 'uint256'}, {'indexed': False, 'name': 'at', 'type': 'uint256'}], 'name': 'PriceUpdate', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': False, 'name': 'prover', 'type': 'address'}], 'name': 'SetProver', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'name': 'previous_owner', 'type': 'address'}, {'indexed': True, 'name': 'new_owner', 'type': 'address'}], 'name': 'OwnershipTransferred', 'type': 'event'}, {'inputs': [{'name': 'new_owner', 'type': 'address'}], 'name': 'transfer_ownership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'renounce_ownership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'pricePerShare', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': 'ts', 'type': 'uint256'}], 'name': 'pricePerShare', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'pricePerAsset', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': 'ts', 'type': 'uint256'}], 'name': 'pricePerAsset', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'price_oracle', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': 'i', 'type': 'uint256'}], 'name': 'price_oracle', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_parameters', 'type': 'uint256[8]'}], 'name': 'update_price', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'name': '_max_acceleration', 'type': 'uint256'}], 'name': 'set_max_acceleration', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'name': '_prover', 'type': 'address'}], 'name': 'set_prover', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'prover', 'outputs': [{'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'price', 'outputs': [{'components': [{'name': 'previous', 'type': 'uint256'}, {'name': 'future', 'type': 'uint256'}], 'name': '', 'type': 'tuple'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'time', 'outputs': [{'components': [{'name': 'previous', 'type': 'uint256'}, {'name': 'future', 'type': 'uint256'}], 'name': '', 'type': 'tuple'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'max_acceleration', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initial_price', 'type': 'uint256'}, {'name': '_max_acceleration', 'type': 'uint256'}], 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'constructor'}]) + elif VERSION == "ScrvusdOracleV1": + soracle = l2_web3.eth.contract(S_ORACLE, abi=[{'anonymous': False, 'inputs': [{'indexed': False, 'name': 'new_price', 'type': 'uint256'}, {'indexed': False, 'name': 'price_params_ts', 'type': 'uint256'}], 'name': 'PriceUpdate', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': False, 'name': 'prover', 'type': 'address'}], 'name': 'SetProver', 'type': 'event'}, {'anonymous': False, 'inputs': [{'indexed': True, 'name': 'previous_owner', 'type': 'address'}, {'indexed': True, 'name': 'new_owner', 'type': 'address'}], 'name': 'OwnershipTransferred', 'type': 'event'}, {'inputs': [{'name': 'new_owner', 'type': 'address'}], 'name': 'transfer_ownership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'renounce_ownership', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'owner', 'outputs': [{'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'price_v0', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_i', 'type': 'uint256'}], 'name': 'price_v0', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'price_v1', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_i', 'type': 'uint256'}], 'name': 'price_v1', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'raw_price', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_i', 'type': 'uint256'}], 'name': 'raw_price', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_i', 'type': 'uint256'}, {'name': '_ts', 'type': 'uint256'}], 'name': 'raw_price', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_parameters', 'type': 'uint256[7]'}, {'name': 'ts', 'type': 'uint256'}], 'name': 'update_price', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'name': '_max_acceleration', 'type': 'uint256'}], 'name': 'set_max_acceleration', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [{'name': '_prover', 'type': 'address'}], 'name': 'set_prover', 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'function'}, {'inputs': [], 'name': 'version', 'outputs': [{'name': '', 'type': 'string'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'prover', 'outputs': [{'name': '', 'type': 'address'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [], 'name': 'max_acceleration', 'outputs': [{'name': '', 'type': 'uint256'}], 'stateMutability': 'view', 'type': 'function'}, {'inputs': [{'name': '_initial_price', 'type': 'uint256'}, {'name': '_max_acceleration', 'type': 'uint256'}], 'outputs': [], 'stateMutability': 'nonpayable', 'type': 'constructor'}]) + + while True: + if time_to_update(scrvusd, soracle): + try: + prove(boracle, prover, block_number=BLOCK_NUMBER) + global last_update + last_update = time.time() + except Exception as e: + print(e) + sleep(12) + + +if __name__ == '__main__': + loop() diff --git a/scripts/submit_scrvusd_price.py b/scripts/submit_scrvusd_price.py new file mode 100644 index 0000000..78a0233 --- /dev/null +++ b/scripts/submit_scrvusd_price.py @@ -0,0 +1,97 @@ +import eth_abi +import rlp +import web3 +from hexbytes import HexBytes + +BLOCK_NUMBER = 18578883 +SCRVUSD = "0x0655977FEb2f289A4aB78af67BAB0d17aAb84367" + +PROVER = "" + +ASSET_PARAM_SLOTS = [ + 21, # total_debt + 22, # total_idle, slot doesn't exist +] +SUPPLY_PARAM_SLOTS = [ + 20, # totalSupply + 38, # full_profit_unlock_date + 39, # profit_unlocking_rate + 40, # last_profit_update + web3.Web3.keccak(eth_abi.encode(["(uint256,address)"], [[18, SCRVUSD]])), # balance_of_self + # ts from block header +] + +# https://github.com/ethereum/go-ethereum/blob/master/core/types/block.go#L69 +BLOCK_HEADER = ( + "parentHash", + "sha3Uncles", + "miner", + "stateRoot", + "transactionsRoot", + "receiptsRoot", + "logsBloom", + "difficulty", + "number", + "gasLimit", + "gasUsed", + "timestamp", + "extraData", + "mixHash", + "nonce", + "baseFeePerGas", # added by EIP-1559 and is ignored in legacy headers + "withdrawalsRoot", # added by EIP-4895 and is ignored in legacy headers + "blobGasUsed", # added by EIP-4844 and is ignored in legacy headers + "excessBlobGas", # added by EIP-4844 and is ignored in legacy headers + "parentBeaconBlockRoot", # added by EIP-4788 and is ignored in legacy headers +) + + +def serialize_block(block): + block_header = [ + HexBytes("0x") if (isinstance((v := block[k]), int) and v == 0) or v == "0x0" else HexBytes(block[k]) + for k in BLOCK_HEADER + if k in block + ] + block_header[14] = HexBytes("0x0000000000000000") # nonce + return rlp.encode(block_header) + + +def serialize_proofs(proofs): + account_proof = list(map(rlp.decode, map(HexBytes, proofs["accountProof"]))) + storage_proofs = [ + list(map(rlp.decode, map(HexBytes, proof["proof"]))) for proof in proofs["storageProof"] + ] + return rlp.encode([account_proof, *storage_proofs]) + + +def generate_proof(eth_web3, block_number=BLOCK_NUMBER, log=False): + block = eth_web3.eth.get_block(block_number) + if log: + print(f"Generating proof for block {block.number}, {block.hash.hex()}") + block_header_rlp = serialize_block(block) + proof_rlp = serialize_proofs(eth_web3.eth.get_proof(SCRVUSD, ASSET_PARAM_SLOTS + SUPPLY_PARAM_SLOTS, block_number)) + + with open("header.txt", "w") as f: + f.write(block_header_rlp.hex()) + with open("proof.txt", "w") as f: + f.write(proof_rlp.hex()) + + return block_header_rlp.hex(), proof_rlp.hex() + + +def submit_proof(proofs, prover=PROVER): + if proofs: + block_header_rlp, proof_rlp = proofs + else: + with open("header.txt") as f: + block_header_rlp = f.read() + with open("proof.txt") as f: + proof_rlp = f.read() + + if isinstance(prover, str): + from brownie import accounts, ScrvusdProver + dev = accounts.load("dev") + prover = ScrvusdProver.at(prover) + prover.prove(bytes.fromhex(block_header_rlp), bytes.fromhex(proof_rlp), {"from": dev}) + else: + prover.prove(bytes.fromhex(block_header_rlp), bytes.fromhex(proof_rlp))